diff options
540 files changed, 16656 insertions, 6002 deletions
diff --git a/Android.bp b/Android.bp index cf3c01755a7d..d64951c3d98f 100644 --- a/Android.bp +++ b/Android.bp @@ -172,6 +172,7 @@ java_library_with_nonpublic_deps { "framework-supplementalprocess.stubs.module_lib", "framework-tethering.stubs.module_lib", "framework-uwb.stubs.module_lib", + "framework-nearby.stubs.module_lib", "framework-wifi.stubs.module_lib", ], soong_config_variables: { @@ -203,6 +204,7 @@ java_library_with_nonpublic_deps { "framework-statsd.impl", "framework-supplementalprocess.impl", "framework-tethering.impl", + "framework-nearby.impl", "framework-uwb.impl", "framework-wifi.impl", "updatable-media", diff --git a/MULTIUSER_OWNERS b/MULTIUSER_OWNERS index fbc611a39d7d..9d92e0fc50f7 100644 --- a/MULTIUSER_OWNERS +++ b/MULTIUSER_OWNERS @@ -1,4 +1,5 @@ # OWNERS of Multiuser related files bookatz@google.com +olilan@google.com omakoto@google.com yamasani@google.com diff --git a/StubLibraries.bp b/StubLibraries.bp index 3b38860cb0fe..1e16f9431f82 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -67,19 +67,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -123,19 +117,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -159,37 +147,25 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "removed.txt", tag: ".removed-api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -218,19 +194,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -298,10 +268,7 @@ java_defaults { java_version: "1.8", compile_dex: true, dist: { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], tag: ".jar", dest: "android-non-updatable.jar", }, @@ -378,10 +345,7 @@ java_library_with_nonpublic_deps { java_defaults { name: "android_stubs_dists_default", dist: { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], tag: ".jar", dest: "android.jar", }, @@ -427,10 +391,7 @@ java_library_with_nonpublic_deps { dists: [ { // Legacy dist path - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], tag: ".jar", dest: "android_system.jar", }, @@ -470,6 +431,7 @@ java_library { static_libs: [ "android-non-updatable.stubs.module_lib", "art.module.public.api.stubs.module_lib", + "i18n.module.public.api.stubs", ], dist: { dir: "apistubs/android/module-lib", diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java index 530dc9dce1ef..15a65ce29bd9 100644 --- a/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java +++ b/apct-tests/perftests/utils/src/android/perftests/utils/TestPackageInstaller.java @@ -130,7 +130,8 @@ public class TestPackageInstaller { IntentSender getIntentSender(int sessionId) { String action = BROADCAST_ACTION + "." + sessionId; IntentFilter filter = new IntentFilter(action); - mContext.registerReceiver(this, filter); + mContext.registerReceiver(this, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); Intent intent = new Intent(action); PendingIntent pending = PendingIntent.getBroadcast(mContext, sessionId, intent, diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java index c251529ae0b1..e5b07429a5c6 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobService.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java @@ -125,7 +125,7 @@ public abstract class JobService extends Service { * will not be invoked. * * @param params Parameters specifying info about this job, including the optional - * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle). + * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle)}. * This object serves to identify this specific running job instance when calling * {@link #jobFinished(JobParameters, boolean)}. * @return {@code true} if your service will continue running, using a separate thread diff --git a/api/Android.bp b/api/Android.bp index 51422b31918a..2e49a0c56778 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -124,10 +124,7 @@ genrule { dest: "current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android.txt", }, @@ -217,10 +214,7 @@ genrule { dest: "removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "removed.txt", }, @@ -260,10 +254,7 @@ genrule { dest: "system-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android.txt", }, @@ -322,10 +313,7 @@ genrule { dest: "system-removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "removed.txt", }, @@ -366,10 +354,7 @@ genrule { dest: "module-lib-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android.txt", }, @@ -430,10 +415,7 @@ genrule { dest: "module-lib-removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "removed.txt", }, @@ -474,10 +456,7 @@ genrule { dest: "system-server-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android.txt", }, @@ -501,10 +480,7 @@ genrule { dest: "system-server-removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "removed.txt", }, @@ -546,9 +522,6 @@ genrule { tools: ["api_versions_trimmer"], cmd: "$(location api_versions_trimmer) $(out) $(in)", dist: { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], }, } diff --git a/core/api/current.txt b/core/api/current.txt index 98d3a91c9944..b983617445e5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3014,6 +3014,8 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); + method public boolean clearCache(); + method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public final void disableSelf(); method public final boolean dispatchGesture(@NonNull android.accessibilityservice.GestureDescription, @Nullable android.accessibilityservice.AccessibilityService.GestureResultCallback, @Nullable android.os.Handler); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); @@ -3028,6 +3030,8 @@ package android.accessibilityservice { method @NonNull public final android.accessibilityservice.TouchInteractionController getTouchInteractionController(int); method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); method @NonNull public final android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays(); + method public boolean isCacheEnabled(); + method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public final android.os.IBinder onBind(android.content.Intent); method @Deprecated protected boolean onGesture(int); @@ -3038,6 +3042,7 @@ package android.accessibilityservice { method public void onSystemActionsChanged(); method public final boolean performGlobalAction(int); method public void setAccessibilityFocusAppearance(int, @ColorInt int); + method public boolean setCacheEnabled(boolean); method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region); @@ -4464,6 +4469,7 @@ package android.app { method public android.app.ActivityOptions setLaunchDisplayId(int); method public android.app.ActivityOptions setLockTaskEnabled(boolean); method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean); + method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time"; @@ -16525,6 +16531,7 @@ package android.graphics { method @NonNull public static android.graphics.RenderEffect createColorFilterEffect(@NonNull android.graphics.ColorFilter); method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float); method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float, @NonNull android.graphics.RenderEffect); + method @NonNull public static android.graphics.RenderEffect createRuntimeShaderEffect(@NonNull android.graphics.RuntimeShader, @NonNull String); method @NonNull public static android.graphics.RenderEffect createShaderEffect(@NonNull android.graphics.Shader); } @@ -20245,6 +20252,7 @@ package android.location { public final class GnssMeasurementRequest implements android.os.Parcelable { method public int describeContents(); + method @IntRange(from=0) public int getIntervalMillis(); method public boolean isFullTracking(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementRequest> CREATOR; @@ -20255,6 +20263,7 @@ package android.location { ctor public GnssMeasurementRequest.Builder(@NonNull android.location.GnssMeasurementRequest); method @NonNull public android.location.GnssMeasurementRequest build(); method @NonNull public android.location.GnssMeasurementRequest.Builder setFullTracking(boolean); + method @NonNull public android.location.GnssMeasurementRequest.Builder setIntervalMillis(@IntRange(from=0) int); } public final class GnssMeasurementsEvent implements android.os.Parcelable { @@ -32476,6 +32485,7 @@ package android.os { public final class SystemClock { method @NonNull public static java.time.Clock currentGnssTimeClock(); + method @NonNull public static java.time.Clock currentNetworkTimeClock(); method public static long currentThreadTimeMillis(); method public static long elapsedRealtime(); method public static long elapsedRealtimeNanos(); @@ -35711,6 +35721,7 @@ package android.provider { field public static final String ACTION_APPLICATION_DETAILS_SETTINGS = "android.settings.APPLICATION_DETAILS_SETTINGS"; field public static final String ACTION_APPLICATION_DEVELOPMENT_SETTINGS = "android.settings.APPLICATION_DEVELOPMENT_SETTINGS"; field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS"; + field public static final String ACTION_APP_LOCALE_SETTINGS = "android.settings.APP_LOCALE_SETTINGS"; field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS"; field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS"; field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS"; @@ -41472,6 +41483,7 @@ package android.telephony { field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool"; field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int"; field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array"; + field public static final String KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL = "enable_cross_sim_calling_on_opportunistic_data_bool"; field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool"; field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool"; field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int"; @@ -43938,7 +43950,7 @@ package android.telephony.euicc { method public boolean isSimPortAvailable(int); method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException; method @Deprecated @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); - method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccManager.ResultListener); + method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull android.app.PendingIntent); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent); field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; field public static final String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE"; @@ -43984,10 +43996,6 @@ package android.telephony.euicc { field public static final int OPERATION_SYSTEM = 1; // 0x1 } - public static interface EuiccManager.ResultListener { - method public void onComplete(int, @Nullable android.content.Intent); - } - } package android.telephony.gsm { @@ -51384,9 +51392,9 @@ package android.view.accessibility { method public int getRecordCount(); method public int getWindowChanges(); method public void initFromParcel(android.os.Parcel); - method public static android.view.accessibility.AccessibilityEvent obtain(int); - method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent); - method public static android.view.accessibility.AccessibilityEvent obtain(); + method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int); + method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent); + method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(); method public void setAction(int); method public void setContentChangeTypes(int); method public void setEventTime(long); @@ -51574,13 +51582,13 @@ package android.view.accessibility { method public boolean isShowingHintText(); method public boolean isTextEntryKey(); method public boolean isVisibleToUser(); - method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View); - method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int); - method public static android.view.accessibility.AccessibilityNodeInfo obtain(); - method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo); method public boolean performAction(int); method public boolean performAction(int, android.os.Bundle); - method public void recycle(); + method @Deprecated public void recycle(); method public boolean refresh(); method public boolean refreshWithExtraData(String, android.os.Bundle); method @Deprecated public void removeAction(int); @@ -51759,8 +51767,8 @@ package android.view.accessibility { method public int getRowCount(); method public int getSelectionMode(); method public boolean isHierarchical(); - method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean); - method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int); field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2 field public static final int SELECTION_MODE_NONE = 0; // 0x0 field public static final int SELECTION_MODE_SINGLE = 1; // 0x1 @@ -51777,9 +51785,9 @@ package android.view.accessibility { 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); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean); + method @Deprecated @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean); } public static final class AccessibilityNodeInfo.CollectionItemInfo.Builder { @@ -51807,7 +51815,7 @@ package android.view.accessibility { method public float getMax(); method public float getMin(); method public int getType(); - method public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float); + method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float); field public static final int RANGE_TYPE_FLOAT = 1; // 0x1 field public static final int RANGE_TYPE_INT = 0; // 0x0 field public static final int RANGE_TYPE_PERCENT = 2; // 0x2 @@ -51861,9 +51869,9 @@ package android.view.accessibility { method public boolean isFullScreen(); method public boolean isPassword(); method public boolean isScrollable(); - method public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord); - method public static android.view.accessibility.AccessibilityRecord obtain(); - method public void recycle(); + method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord); + method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain(); + method @Deprecated public void recycle(); method public void setAddedCount(int); method public void setBeforeText(CharSequence); method public void setChecked(boolean); @@ -57667,6 +57675,8 @@ package android.window { method public void clearOnExitAnimationListener(); method public void setOnExitAnimationListener(@NonNull android.window.SplashScreen.OnExitAnimationListener); method public void setSplashScreenTheme(@StyleRes int); + field public static final int SPLASH_SCREEN_STYLE_EMPTY = 0; // 0x0 + field public static final int SPLASH_SCREEN_STYLE_ICON = 1; // 0x1 } public static interface SplashScreen.OnExitAnimationListener { diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 04c1e9edbd4b..53bc8a6e0323 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -258,6 +258,43 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR; } + public final class NetworkTemplate implements android.os.Parcelable { + method public int describeContents(); + method public int getDefaultNetworkStatus(); + method public int getMatchRule(); + method public int getMeteredness(); + method public int getOemManaged(); + method public int getRatType(); + method public int getRoaming(); + method @NonNull public java.util.Set<java.lang.String> getSubscriberIds(); + method @NonNull public java.util.Set<java.lang.String> getWifiNetworkKeys(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkTemplate> CREATOR; + field public static final int MATCH_BLUETOOTH = 8; // 0x8 + field public static final int MATCH_CARRIER = 10; // 0xa + field public static final int MATCH_ETHERNET = 5; // 0x5 + field public static final int MATCH_MOBILE = 1; // 0x1 + field public static final int MATCH_WIFI = 4; // 0x4 + field public static final int NETWORK_TYPE_ALL = -1; // 0xffffffff + field public static final int OEM_MANAGED_ALL = -1; // 0xffffffff + field public static final int OEM_MANAGED_NO = 0; // 0x0 + field public static final int OEM_MANAGED_PAID = 1; // 0x1 + field public static final int OEM_MANAGED_PRIVATE = 2; // 0x2 + field public static final int OEM_MANAGED_YES = -2; // 0xfffffffe + } + + public static final class NetworkTemplate.Builder { + ctor public NetworkTemplate.Builder(int); + method @NonNull public android.net.NetworkTemplate build(); + method @NonNull public android.net.NetworkTemplate.Builder setDefaultNetworkStatus(int); + method @NonNull public android.net.NetworkTemplate.Builder setMeteredness(int); + method @NonNull public android.net.NetworkTemplate.Builder setOemManaged(int); + method @NonNull public android.net.NetworkTemplate.Builder setRatType(int); + method @NonNull public android.net.NetworkTemplate.Builder setRoaming(int); + method @NonNull public android.net.NetworkTemplate.Builder setSubscriberIds(@NonNull java.util.Set<java.lang.String>); + method @NonNull public android.net.NetworkTemplate.Builder setWifiNetworkKeys(@NonNull java.util.Set<java.lang.String>); + } + public class NetworkWatchlistManager { method @Nullable public byte[] getWatchlistConfigHash(); } @@ -342,6 +379,10 @@ package android.os { method @Nullable public android.os.IBinder getOrThrow() throws android.os.StatsServiceManager.ServiceNotFoundException; } + public final class StrictMode { + method public static void noteUntaggedSocket(); + } + public class SystemConfigManager { method @NonNull public java.util.List<android.content.ComponentName> getEnabledComponentOverrides(@NonNull String); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 75c9be966f81..554601a3074e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -298,6 +298,7 @@ package android { field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS"; field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"; field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND"; + field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS"; field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE"; field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES"; field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"; @@ -341,6 +342,7 @@ package android { public static final class R.array { field public static final int config_keySystemUuidMapping = 17235973; // 0x1070005 + field public static final int config_optionalIpSecAlgorithms; } public static final class R.attr { @@ -2591,6 +2593,7 @@ package android.content { field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding"; field public static final String MUSIC_RECOGNITION_SERVICE = "music_recognition"; + field public static final String NEARBY_SERVICE = "nearby"; field public static final String NETD_SERVICE = "netd"; field @Deprecated public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; @@ -2661,6 +2664,7 @@ package android.content { field public static final String ACTION_PENDING_INCIDENT_REPORTS_CHANGED = "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED"; field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; + field public static final String ACTION_REFRESH_SAFETY_SOURCES = "android.intent.action.REFRESH_SAFETY_SOURCES"; field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; field @RequiresPermission(android.Manifest.permission.REVIEW_ACCESSIBILITY_SERVICES) public static final String ACTION_REVIEW_ACCESSIBILITY_SERVICES = "android.intent.action.REVIEW_ACCESSIBILITY_SERVICES"; field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_ONGOING_PERMISSION_USAGE = "android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE"; @@ -2691,6 +2695,10 @@ package android.content { field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; field public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; field public static final String EXTRA_REASON = "android.intent.extra.REASON"; + field public static final int EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA = 0; // 0x0 + field public static final int EXTRA_REFRESH_REQUEST_TYPE_GET_DATA = 1; // 0x1 + field public static final String EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE = "android.intent.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE"; + field public static final String EXTRA_REFRESH_SAFETY_SOURCE_IDS = "android.intent.extra.REFRESH_SAFETY_SOURCE_IDS"; field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME"; @@ -2910,6 +2918,7 @@ package android.content.pm { } public static class LauncherApps.ShortcutQuery { + field public static final int FLAG_GET_PERSISTED_DATA = 4096; // 0x1000 field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800 } @@ -5138,6 +5147,9 @@ package android.location { public static final class GnssSingleSatCorrection.Builder { ctor public GnssSingleSatCorrection.Builder(); method @NonNull public android.location.GnssSingleSatCorrection build(); + method @NonNull public android.location.GnssSingleSatCorrection.Builder clearExcessPathLengthMeters(); + method @NonNull public android.location.GnssSingleSatCorrection.Builder clearExcessPathLengthUncertaintyMeters(); + method @NonNull public android.location.GnssSingleSatCorrection.Builder clearProbabilityLineOfSight(); method @NonNull public android.location.GnssSingleSatCorrection.Builder setCarrierFrequencyHz(@FloatRange(from=0.0f, fromInclusive=false) float); method @NonNull public android.location.GnssSingleSatCorrection.Builder setConstellationType(int); method @NonNull public android.location.GnssSingleSatCorrection.Builder setExcessPathLengthMeters(@FloatRange(from=0.0f) float); @@ -5663,6 +5675,7 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat); method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes); + method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int); method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); @@ -6469,10 +6482,11 @@ package android.media.tv.tuner { method public int getAvSyncHwId(@NonNull android.media.tv.tuner.filter.Filter); method public long getAvSyncTime(int); method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getAvailableFrontendInfos(); - method @Nullable public String getCurrentFrontendHardwardInfo(); + method @Nullable public String getCurrentFrontendHardwareInfo(); method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities(); method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo(); method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]); + method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int); method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int); method public boolean isLowestPriority(int); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler(); @@ -6485,6 +6499,7 @@ package android.media.tv.tuner { method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter(); method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback); method public int setLnaEnabled(boolean); + method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int); method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener); method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); @@ -7917,7 +7932,7 @@ package android.media.tv.tuner.frontend { method public void onScanStopped(); method public void onSignalTypeReported(int); method public void onSymbolRatesReported(@NonNull int[]); - method public default void onUnLocked(); + method public default void onUnlocked(); } } @@ -8077,13 +8092,17 @@ package android.net { method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR; + field public static final int DEFAULT_NETWORK_ALL = -1; // 0xffffffff field public static final int DEFAULT_NETWORK_NO = 0; // 0x0 field public static final int DEFAULT_NETWORK_YES = 1; // 0x1 field public static final String IFACE_VT = "vt_data0"; + field public static final int METERED_ALL = -1; // 0xffffffff field public static final int METERED_NO = 0; // 0x0 field public static final int METERED_YES = 1; // 0x1 + field public static final int ROAMING_ALL = -1; // 0xffffffff field public static final int ROAMING_NO = 0; // 0x0 field public static final int ROAMING_YES = 1; // 0x1 + field public static final int SET_ALL = -1; // 0xffffffff field public static final int SET_DEFAULT = 0; // 0x0 field public static final int SET_FOREGROUND = 1; // 0x1 field public static final int TAG_NONE = 0; // 0x0 @@ -9527,6 +9546,7 @@ package android.permission { public final class PermissionControllerManager { method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>); + method public void getUnusedAppCount(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback); method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle); field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1 @@ -9551,6 +9571,7 @@ package android.permission { method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>); method @BinderThread public void onGetPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>); method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable); method @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>); @@ -9577,6 +9598,7 @@ package android.permission { method @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public void setRuntimePermissionsVersion(@IntRange(from=0) int); method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int); method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void stopOneTimePermissionSession(@NonNull String); + field @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS) public static final String ACTION_REVIEW_PERMISSION_DECISIONS = "android.permission.action.REVIEW_PERMISSION_DECISIONS"; field public static final int PERMISSION_GRANTED = 0; // 0x0 field public static final int PERMISSION_HARD_DENIED = 2; // 0x2 field public static final int PERMISSION_SOFT_DENIED = 1; // 0x1 @@ -9998,7 +10020,7 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String); field public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios"; field public static final String APP_STANDBY_ENABLED = "app_standby_enabled"; - field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages"; + field @Deprecated public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages"; field public static final String CARRIER_APP_NAMES = "carrier_app_names"; field public static final String CARRIER_APP_WHITELIST = "carrier_app_whitelist"; field public static final String DEFAULT_SM_DP_PLUS = "default_sm_dp_plus"; @@ -10712,6 +10734,7 @@ package android.service.euicc { field public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED = "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED"; field public static final String EXTRA_RESOLUTION_CONSENT = "android.service.euicc.extra.RESOLUTION_CONSENT"; field public static final String EXTRA_RESOLUTION_PORT_INDEX = "android.service.euicc.extra.RESOLUTION_PORT_INDEX"; + field public static final String EXTRA_RESOLUTION_USE_PORT_INDEX = "android.service.euicc.extra.RESOLUTION_USE_PORT_INDEX"; field public static final String EXTRA_RESOLVABLE_ERRORS = "android.service.euicc.extra.RESOLVABLE_ERRORS"; field public static final int RESOLVABLE_ERROR_CONFIRMATION_CODE = 1; // 0x1 field public static final int RESOLVABLE_ERROR_POLICY_RULES = 2; // 0x2 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index fad681f077f0..8724b5363985 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -59,6 +59,7 @@ package android { public static final class R.bool { field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005 field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004 + field public static final int config_preventImeStartupUnlessTextEditor; field public static final int config_remoteInsetsControllerControlsSystemBars = 17891334; // 0x1110006 } @@ -2086,6 +2087,7 @@ package android.provider { field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis"; field public static final String NAMESPACE_DEVICE_IDLE = "device_idle"; field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler"; + field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar"; } public final class Settings { @@ -2836,7 +2838,6 @@ package android.view.accessibility { method public void addChild(@NonNull android.os.IBinder); method public long getSourceNodeId(); method public void setLeashedParent(@Nullable android.os.IBinder, int); - method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); method public void writeToParcelNoRecycle(android.os.Parcel, int); } @@ -2876,6 +2877,7 @@ package android.view.autofill { } public final class AutofillManager { + field public static final String DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "compat_mode_allowed_packages"; field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes"; field public static final int FLAG_SMART_SUGGESTION_OFF = 0; // 0x0 field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1 diff --git a/core/java/Android.bp b/core/java/Android.bp index 343830a468dc..c9cbef2c9e9a 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -301,6 +301,17 @@ cc_library { ], } +cc_library { + name: "libactivity_manager_procstate_aidl-cpp", + host_supported: true, + srcs: [ + ":activity_manager_procstate_aidl", + ], + aidl: { + export_aidl_headers: true, + }, +} + // Build Rust bindings for PermissionController. Needed by keystore2. aidl_interface { name: "android.os.permissions_aidl", diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3d3855164274..9a5586747d0e 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -59,6 +59,7 @@ import android.view.MotionEvent; import android.view.SurfaceView; import android.view.WindowManager; import android.view.WindowManagerImpl; +import android.view.accessibility.AccessibilityCache; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; @@ -748,7 +749,6 @@ public abstract class AccessibilityService extends Service { private FingerprintGestureController mFingerprintGestureController; - /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. * @@ -2076,6 +2076,85 @@ public abstract class AccessibilityService extends Service { available); } + /** Sets the cache status. + * + * <p>If {@code enabled}, enable the cache and prefetching. Otherwise, disable the cache + * and prefetching. + * Note: By default the cache is enabled. + * @param enabled whether to enable or disable the cache. + * @return {@code true} if the cache and connection are not null, so the cache status is set. + */ + public boolean setCacheEnabled(boolean enabled) { + AccessibilityCache cache = + AccessibilityInteractionClient.getCache(mConnectionId); + if (cache == null) { + return false; + } + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getConnection(mConnectionId); + if (connection == null) { + return false; + } + try { + connection.setCacheEnabled(enabled); + cache.setEnabled(enabled); + return true; + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while setting status of cache", re); + re.rethrowFromSystemServer(); + } + return false; + } + + /** Invalidates {@code node} and its subtree in the cache. + * @param node the node to invalidate. + * @return {@code true} if the subtree rooted at {@code node} was invalidated. + */ + public boolean clearCachedSubtree(@NonNull AccessibilityNodeInfo node) { + AccessibilityCache cache = + AccessibilityInteractionClient.getCache(mConnectionId); + if (cache == null) { + return false; + } + return cache.clearSubTree(node); + } + + /** Clears the cache. + * @return {@code true} if the cache was cleared + */ + public boolean clearCache() { + AccessibilityCache cache = + AccessibilityInteractionClient.getCache(mConnectionId); + if (cache == null) { + return false; + } + cache.clear(); + return true; + } + + /** Checks if {@code node} is in the cache. + * @param node the node to check. + * @return {@code true} if {@code node} is in the cache. + */ + public boolean isNodeInCache(@NonNull AccessibilityNodeInfo node) { + AccessibilityCache cache = + AccessibilityInteractionClient.getCache(mConnectionId); + if (cache == null) { + return false; + } + return cache.isNodeInCache(node); + } + + /** Returns {@code true} if the cache is enabled. */ + public boolean isCacheEnabled() { + AccessibilityCache cache = + AccessibilityInteractionClient.getCache(mConnectionId); + if (cache == null) { + return false; + } + return cache.isEnabled(); + } + /** This is called when the system action list is changed. */ public void onSystemActionsChanged() { } @@ -2613,11 +2692,11 @@ public abstract class AccessibilityService extends Service { mCallback.init(mConnectionId, windowToken); mCallback.onServiceConnected(); } else { + AccessibilityInteractionClient.getInstance(mContext) + .clearCache(mConnectionId); AccessibilityInteractionClient.getInstance(mContext).removeConnection( mConnectionId); mConnectionId = AccessibilityInteractionClient.NO_ID; - AccessibilityInteractionClient.getInstance(mContext) - .clearCache(mConnectionId); mCallback.init(AccessibilityInteractionClient.NO_ID, null); } return; diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 93e69145f90c..7d76bbf35081 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -124,6 +124,8 @@ interface IAccessibilityServiceConnection { void setFocusAppearance(int strokeWidth, int color); + void setCacheEnabled(boolean enabled); + oneway void logTrace(long timestamp, String where, long loggingTypes, String callingParams, int processId, long threadId, int callingUid, in Bundle serializedCallingStackInBundle); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 11a2edea77dd..cf2b7aca8e52 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6191,18 +6191,20 @@ public class Activity extends ContextThemeWrapper @Nullable public Uri getReferrer() { Intent intent = getIntent(); - try { - Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER); - if (referrer != null) { - return referrer; - } - String referrerName = intent.getStringExtra(Intent.EXTRA_REFERRER_NAME); - if (referrerName != null) { - return Uri.parse(referrerName); + if (intent != null) { + try { + Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER); + if (referrer != null) { + return referrer; + } + String referrerName = intent.getStringExtra(Intent.EXTRA_REFERRER_NAME); + if (referrerName != null) { + return Uri.parse(referrerName); + } + } catch (BadParcelableException e) { + Log.w(TAG, "Cannot read referrer from intent;" + + " intent extras contain unknown custom Parcelable objects"); } - } catch (BadParcelableException e) { - Log.w(TAG, "Cannot read referrer from intent;" - + " intent extras contain unknown custom Parcelable objects"); } if (mReferrer != null) { return new Uri.Builder().scheme("android-app").authority(mReferrer).build(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index f7d5e52e3675..5e5649f4eadf 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -337,7 +337,7 @@ public class ActivityOptions extends ComponentOptions { private static final String KEY_LAUNCHED_FROM_BUBBLE = "android.activity.launchTypeBubble"; - /** See {@link #setSplashscreenStyle(int)}. */ + /** See {@link #setSplashScreenStyle(int)}. */ private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; @@ -1393,20 +1393,27 @@ public class ActivityOptions extends ComponentOptions { } /** - * Sets the preferred splash screen style. + * Gets the style can be used for cold-launching an activity. + * @see #setSplashScreenStyle(int) * @hide */ - public void setSplashscreenStyle(@SplashScreen.SplashScreenStyle int style) { - mSplashScreenStyle = style; + public @SplashScreen.SplashScreenStyle int getSplashScreenStyle() { + return mSplashScreenStyle; } /** - * Gets the preferred splash screen style from caller - * @hide + * Sets the preferred splash screen style of the opening activities. This only applies if the + * Activity or Process is not yet created. + * @param style Can be either {@link SplashScreen#SPLASH_SCREEN_STYLE_ICON} or + * {@link SplashScreen#SPLASH_SCREEN_STYLE_EMPTY} */ - @SplashScreen.SplashScreenStyle - public int getSplashScreenStyle() { - return mSplashScreenStyle; + @NonNull + public ActivityOptions setSplashScreenStyle(@SplashScreen.SplashScreenStyle int style) { + if (style == SplashScreen.SPLASH_SCREEN_STYLE_ICON + || style == SplashScreen.SPLASH_SCREEN_STYLE_EMPTY) { + mSplashScreenStyle = style; + } + return this; } /** diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a8894dcd0661..1778ea4a32e2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -243,6 +243,7 @@ import java.util.Map; import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; /** @@ -344,11 +345,9 @@ public final class ActivityThread extends ClientTransactionHandler */ @UnsupportedAppUsage final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); - /** - * Maps from activity token to local record of the activities that are preparing to be launched. - */ - final Map<IBinder, ActivityClientRecord> mLaunchingActivities = - Collections.synchronizedMap(new ArrayMap<IBinder, ActivityClientRecord>()); + /** Maps from activity token to the pending override configuration. */ + @GuardedBy("mPendingOverrideConfigs") + private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>(); /** The activities to be truly destroyed (not include relaunch). */ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed = Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>()); @@ -358,6 +357,7 @@ public final class ActivityThread extends ClientTransactionHandler // Number of activities that are currently visible on-screen. @UnsupportedAppUsage int mNumVisibleActivities = 0; + private final AtomicInteger mNumLaunchingActivities = new AtomicInteger(); @GuardedBy("mAppThread") private int mLastProcessState = PROCESS_STATE_UNKNOWN; @GuardedBy("mAppThread") @@ -555,10 +555,6 @@ public final class ActivityThread extends ClientTransactionHandler boolean hideForNow; Configuration createdConfig; Configuration overrideConfig; - // Used to save the last reported configuration from server side so that activity - // configuration transactions can always use the latest configuration. - @GuardedBy("this") - private Configuration mPendingOverrideConfig; // Used for consolidating configs before sending on to Activity. private Configuration tmpConfig = new Configuration(); // Callback used for updating activity override config and camera compat control state. @@ -3361,21 +3357,6 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void addLaunchingActivity(IBinder token, ActivityClientRecord activity) { - mLaunchingActivities.put(token, activity); - } - - @Override - public ActivityClientRecord getLaunchingActivity(IBinder token) { - return mLaunchingActivities.get(token); - } - - @Override - public void removeLaunchingActivity(IBinder token) { - mLaunchingActivities.remove(token); - } - - @Override public ActivityClientRecord getActivityClient(IBinder token) { return mActivities.get(token); } @@ -3419,7 +3400,7 @@ public final class ActivityThread extends ClientTransactionHandler // Defer the top state for VM to avoid aggressive JIT compilation affecting activity // launch time. if (processState == ActivityManager.PROCESS_STATE_TOP - && !mLaunchingActivities.isEmpty()) { + && mNumLaunchingActivities.get() > 0) { mPendingProcessState = processState; mH.postDelayed(this::applyPendingProcessState, PENDING_TOP_PROCESS_STATE_TIMEOUT); } else { @@ -3435,7 +3416,7 @@ public final class ActivityThread extends ClientTransactionHandler // Handle the pending configuration if the process state is changed from cached to // non-cached. Except the case where there is a launching activity because the // LaunchActivityItem will handle it. - if (wasCached && !isCachedProcessState() && mLaunchingActivities.isEmpty()) { + if (wasCached && !isCachedProcessState() && mNumLaunchingActivities.get() == 0) { final Configuration pendingConfig = mConfigurationController.getPendingConfiguration(false /* clearPending */); if (pendingConfig == null) { @@ -3473,6 +3454,11 @@ public final class ActivityThread extends ClientTransactionHandler } } + @Override + public void countLaunchingActivities(int num) { + mNumLaunchingActivities.getAndAdd(num); + } + @UnsupportedAppUsage public final void sendActivityResult( IBinder token, String id, int requestCode, @@ -6096,31 +6082,31 @@ public final class ActivityThread extends ClientTransactionHandler } /** - * Sets the supplied {@code overrideConfig} as pending for the {@code activityToken}. Calling + * Sets the supplied {@code overrideConfig} as pending for the {@code token}. Calling * this method prevents any calls to * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int)} from * processing any configurations older than {@code overrideConfig}. */ @Override - public void updatePendingActivityConfiguration(ActivityClientRecord r, - Configuration overrideConfig) { - synchronized (r) { - if (r.mPendingOverrideConfig != null - && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { + public void updatePendingActivityConfiguration(IBinder token, Configuration overrideConfig) { + synchronized (mPendingOverrideConfigs) { + final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(token); + if (pendingOverrideConfig != null + && !pendingOverrideConfig.isOtherSeqNewer(overrideConfig)) { if (DEBUG_CONFIGURATION) { - Slog.v(TAG, "Activity has newer configuration pending so drop this" - + " transaction. overrideConfig=" + overrideConfig - + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig); + Slog.v(TAG, "Activity has newer configuration pending so this transaction will" + + " be dropped. overrideConfig=" + overrideConfig + + " pendingOverrideConfig=" + pendingOverrideConfig); } return; } - r.mPendingOverrideConfig = overrideConfig; + mPendingOverrideConfigs.put(token, overrideConfig); } } /** * Handle new activity configuration and/or move to a different display. This method is a noop - * if {@link #updatePendingActivityConfiguration(ActivityClientRecord, Configuration)} has been + * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been * called with a newer config than {@code overrideConfig}. * * @param r Target activity record. @@ -6131,16 +6117,17 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handleActivityConfigurationChanged(ActivityClientRecord r, @NonNull Configuration overrideConfig, int displayId) { - synchronized (r) { - if (overrideConfig.isOtherSeqNewer(r.mPendingOverrideConfig)) { + synchronized (mPendingOverrideConfigs) { + final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token); + if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) { if (DEBUG_CONFIGURATION) { Slog.v(TAG, "Activity has newer configuration pending so drop this" + " transaction. overrideConfig=" + overrideConfig - + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig); + + " pendingOverrideConfig=" + pendingOverrideConfig); } return; } - r.mPendingOverrideConfig = null; + mPendingOverrideConfigs.remove(r.token); } if (displayId == INVALID_DISPLAY) { diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index c743f6572d5e..d365269ed1a6 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -83,6 +83,9 @@ public abstract class ClientTransactionHandler { /** Set current process state. */ public abstract void updateProcessState(int processState, boolean fromIpc); + /** Count how many activities are launching. */ + public abstract void countLaunchingActivities(int num); + // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions // and deliver callbacks. @@ -139,7 +142,7 @@ public abstract class ClientTransactionHandler { public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start); /** Set pending activity configuration in case it will be updated by other transaction item. */ - public abstract void updatePendingActivityConfiguration(@NonNull ActivityClientRecord r, + public abstract void updatePendingActivityConfiguration(@NonNull IBinder token, Configuration overrideConfig); /** Deliver activity (override) configuration change. */ @@ -189,26 +192,6 @@ public abstract class ClientTransactionHandler { FixedRotationAdjustments fixedRotationAdjustments); /** - * Add {@link ActivityClientRecord} that is preparing to be launched. - * @param token Activity token. - * @param activity An initialized instance of {@link ActivityClientRecord} to use during launch. - */ - public abstract void addLaunchingActivity(IBinder token, ActivityClientRecord activity); - - /** - * Get {@link ActivityClientRecord} that is preparing to be launched. - * @param token Activity token. - * @return An initialized instance of {@link ActivityClientRecord} to use during launch. - */ - public abstract ActivityClientRecord getLaunchingActivity(IBinder token); - - /** - * Remove {@link ActivityClientRecord} from the launching activity list. - * @param token Activity token. - */ - public abstract void removeLaunchingActivity(IBinder token); - - /** * Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the * provided token. */ diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f3e9f105500e..c895636ddc82 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1796,6 +1796,7 @@ class ContextImpl extends Context { && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) { flags = flags | Context.RECEIVER_EXPORTED; } + final Intent intent = ActivityManager.getService().registerReceiverWithFeature( mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId, @@ -1810,6 +1811,9 @@ class ContextImpl extends Context { return intent; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (WtfException e) { + Log.wtf(TAG, e.getMessage()); + return null; } } diff --git a/core/java/android/app/WtfException.java b/core/java/android/app/WtfException.java new file mode 100644 index 000000000000..ba8dbefad160 --- /dev/null +++ b/core/java/android/app/WtfException.java @@ -0,0 +1,66 @@ +/* + * 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.app; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Exception meant to be thrown instead of calling Log.wtf() such that server side code can + * throw this exception, and it will carry across the binder to do client side logging. + * {@hide} + */ +public final class WtfException extends RuntimeException implements Parcelable { + public static final @android.annotation.NonNull + Creator<WtfException> CREATOR = new Creator<WtfException>() { + @Override + public WtfException createFromParcel(Parcel source) { + return new WtfException(source.readString8()); + } + + @Override + public WtfException[] newArray(int size) { + return new WtfException[size]; + } + }; + + public WtfException(@android.annotation.NonNull String message) { + super(message); + } + + /** {@hide} */ + public static Throwable readFromParcel(Parcel in) { + final String msg = in.readString8(); + return new WtfException(msg); + } + + /** {@hide} */ + public static void writeToParcel(Parcel out, Throwable t) { + out.writeString8(t.getMessage()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) { + dest.writeString8(getMessage()); + } +} + diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index 032b57e65458..5a3ad310b4d6 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -40,11 +40,9 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @Override public void preExecute(android.app.ClientTransactionHandler client, IBinder token) { - final ActivityClientRecord r = getActivityClientRecord(client, token, - true /* includeLaunching */); // Notify the client of an upcoming change in the token configuration. This ensures that // batches of config change items only process the newest configuration. - client.updatePendingActivityConfiguration(r, mConfiguration); + client.updatePendingActivityConfiguration(token, mConfiguration); } @Override diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java index 186f25deab67..6a6d76d20259 100644 --- a/core/java/android/app/servertransaction/ActivityTransactionItem.java +++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java @@ -53,43 +53,23 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem { public abstract void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions); - @NonNull ActivityClientRecord getActivityClientRecord( - @NonNull ClientTransactionHandler client, IBinder token) { - return getActivityClientRecord(client, token, false /* includeLaunching */); - } - /** * Gets the {@link ActivityClientRecord} instance that corresponds to the provided token. * @param client Target client handler. * @param token Target activity token. - * @param includeLaunching Indicate to find the {@link ActivityClientRecord} in launching - * activity list. - * <p>Note that there is no {@link android.app.Activity} instance in - * {@link ActivityClientRecord} from the launching activity list. * @return The {@link ActivityClientRecord} instance that corresponds to the provided token. */ @NonNull ActivityClientRecord getActivityClientRecord( - @NonNull ClientTransactionHandler client, IBinder token, boolean includeLaunching) { - ActivityClientRecord r = null; - // Check launching Activity first to prevent race condition that activity instance has not - // yet set to ActivityClientRecord. - if (includeLaunching) { - r = client.getLaunchingActivity(token); - } - // Then if we don't want to find launching Activity or the ActivityClientRecord doesn't - // exist in launching Activity list. The ActivityClientRecord should have been initialized - // and put in the Activity list. - if (r == null) { - r = client.getActivityClient(token); - if (r != null && client.getActivity(token) == null) { - throw new IllegalArgumentException("Activity must not be null to execute " - + "transaction item"); - } - } + @NonNull ClientTransactionHandler client, IBinder token) { + final ActivityClientRecord r = client.getActivityClient(token); if (r == null) { throw new IllegalArgumentException("Activity client record must not be null to execute " + "transaction item"); } + if (client.getActivity(token) == null) { + throw new IllegalArgumentException("Activity must not be null to execute " + + "transaction item"); + } return r; } } diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index 4de608b50f6f..0a2503c962d6 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -82,12 +82,7 @@ public class LaunchActivityItem extends ClientTransactionItem { @Override public void preExecute(ClientTransactionHandler client, IBinder token) { - ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, - mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, - mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo, - client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken, - mLaunchedFromBubble); - client.addLaunchingActivity(token, r); + client.countLaunchingActivities(1); client.updateProcessState(mProcState, false); client.updatePendingConfiguration(mCurConfig); if (mActivityClientController != null) { @@ -99,7 +94,11 @@ public class LaunchActivityItem extends ClientTransactionItem { public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); - ActivityClientRecord r = client.getLaunchingActivity(token); + ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, + mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, + mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo, + client, mAssistToken, mFixedRotationAdjustments, mShareableActivityToken, + mLaunchedFromBubble); client.handleLaunchActivity(r, pendingActions, null /* customIntent */); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -107,7 +106,7 @@ public class LaunchActivityItem extends ClientTransactionItem { @Override public void postExecute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { - client.removeLaunchingActivity(token); + client.countLaunchingActivities(-1); } diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java index 4b8a3476262e..2893ff21c3f2 100644 --- a/core/java/android/app/servertransaction/MoveToDisplayItem.java +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -40,11 +40,9 @@ public class MoveToDisplayItem extends ActivityTransactionItem { @Override public void preExecute(ClientTransactionHandler client, IBinder token) { - final ActivityClientRecord r = getActivityClientRecord(client, token, - true /* includeLaunching */); // Notify the client of an upcoming change in the token configuration. This ensures that // batches of config change items only process the newest configuration. - client.updatePendingActivityConfiguration(r, mConfiguration); + client.updatePendingActivityConfiguration(token, mConfiguration); } @Override diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 2d1ecfb5e052..9a0f02e8b2b6 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -3064,6 +3064,9 @@ public final class BluetoothAdapter { BluetoothCsipSetCoordinator csipSetCoordinator = new BluetoothCsipSetCoordinator(context, listener, this); return true; + } else if (profile == BluetoothProfile.LE_CALL_CONTROL) { + BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener); + return true; } else { return false; } @@ -3166,6 +3169,10 @@ public final class BluetoothAdapter { (BluetoothCsipSetCoordinator) proxy; csipSetCoordinator.close(); break; + case BluetoothProfile.LE_CALL_CONTROL: + BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy; + tbs.close(); + break; } } diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index fe8d1ba80e33..b531829d2940 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -87,7 +87,7 @@ public final class BluetoothGatt implements BluetoothProfile { private static final int CONN_STATE_CLOSED = 4; private static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 5; - private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 1000; // milliseconds + private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 10; // milliseconds private List<BluetoothGattService> mServices; diff --git a/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java b/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java new file mode 100644 index 000000000000..cb47280acc7e --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java @@ -0,0 +1,788 @@ +/* + * Copyright 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.bluetooth; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This class represents an LE Audio Broadcast Source and the associated information that is needed + * by Broadcast Audio Scan Service (BASS) residing on a Scan Delegator. + * + * <p>For example, the Scan Delegator on an LE Audio Broadcast Sink can use the information + * contained within an instance of this class to synchronize with an LE Audio Broadcast Source in + * order to listen to a Broadcast Audio Stream. + * + * <p>BroadcastAssistant has a BASS client which facilitates scanning and discovery of Broadcast + * Sources on behalf of say a Broadcast Sink. Upon successful discovery of one or more Broadcast + * sources, this information needs to be communicated to the BASS Server residing within the Scan + * Delegator on a Broadcast Sink. This is achieved using the Periodic Advertising Synchronization + * Transfer (PAST) procedure. This procedure uses information contained within an instance of this + * class. + * + * @hide + */ +public final class BluetoothLeBroadcastSourceInfo implements Parcelable { + private static final String TAG = "BluetoothLeBroadcastSourceInfo"; + private static final boolean DBG = true; + + /** + * Constants representing Broadcast Source address types + * + * @hide + */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_", + value = { + LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC, + LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM, + LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSourceAddressType {} + + /** + * Represents a public address used by an LE Audio Broadcast Source + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC = 0; + + /** + * Represents a random address used by an LE Audio Broadcast Source + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM = 1; + + /** + * Represents an invalid address used by an LE Audio Broadcast Seurce + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID = 0xFFFF; + + /** + * Periodic Advertising Synchronization state + * + * <p>Periodic Advertising (PA) enables the LE Audio Broadcast Assistant to discover broadcast + * audio streams as well as the audio stream configuration on behalf of an LE Audio Broadcast + * Sink. This information can then be transferred to the LE Audio Broadcast Sink using the + * Periodic Advertising Synchronizaton Transfer (PAST) procedure. + * + * @hide + */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_", + value = { + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSinkPaSyncState {} + + /** + * Indicates that the Broadcast Sink is not synchronized with the Periodic Advertisements (PA) + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE = 0; + + /** + * Indicates that the Broadcast Sink requested the Broadcast Assistant to synchronize with the + * Periodic Advertisements (PA). + * + * <p>This is also known as scan delegation or scan offloading. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ = 1; + + /** + * Indicates that the Broadcast Sink is synchronized with the Periodic Advertisements (PA). + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC = 2; + + /** + * Indicates that the Broadcast Sink was unable to synchronize with the Periodic Advertisements + * (PA). + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL = 3; + + /** + * Indicates that the Broadcast Sink should be synchronized with the Periodic Advertisements + * (PA) using the Periodic Advertisements Synchronization Transfert (PAST) procedure. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST = 4; + + /** + * Indicates that the Broadcast Sink synchornization state is invalid. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_", + value = { + LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, + LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSinkAudioSyncState {} + + /** + * Indicates that the Broadcast Sink is not synchronized with a Broadcast Audio Stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0; + + /** + * Indicates that the Broadcast Sink is synchronized with a Broadcast Audio Stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED = 1; + + /** + * Indicates that the Broadcast Sink audio synchronization state is invalid. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SINK_ENC_STATE_", + value = { + LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED, + LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED, + LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING, + LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSinkEncryptionState {} + + /** + * Indicates that the Broadcast Sink is synchronized with an unencrypted audio stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED = 0; + + /** + * Indicates that the Broadcast Sink needs a Broadcast Code to synchronize with the audio + * stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED = 1; + + /** + * Indicates that the Broadcast Sink is synchronized with an encrypted audio stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING = 2; + + /** + * Indicates that the Broadcast Sink is unable to decrypt an audio stream due to an incorrect + * Broadcast Code + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE = 3; + + /** + * Indicates that the Broadcast Sink encryption state is invalid. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID = 0xFF; + + /** + * Represents an invalid LE Audio Broadcast Source ID + * + * @hide + */ + public static final byte LE_AUDIO_BROADCAST_SINK_INVALID_SOURCE_ID = (byte) 0x00; + + /** + * Represents an invalid Broadcast ID of a Broadcast Source + * + * @hide + */ + public static final int INVALID_BROADCAST_ID = 0xFFFFFF; + + private byte mSourceId; + private @LeAudioBroadcastSourceAddressType int mSourceAddressType; + private BluetoothDevice mSourceDevice; + private byte mSourceAdvSid; + private int mBroadcastId; + private @LeAudioBroadcastSinkPaSyncState int mPaSyncState; + private @LeAudioBroadcastSinkEncryptionState int mEncryptionStatus; + private @LeAudioBroadcastSinkAudioSyncState int mAudioSyncState; + private byte[] mBadBroadcastCode; + private byte mNumSubGroups; + private Map<Integer, Integer> mSubgroupBisSyncState = new HashMap<Integer, Integer>(); + private Map<Integer, byte[]> mSubgroupMetadata = new HashMap<Integer, byte[]>(); + + private String mBroadcastCode; + private static final int BIS_NO_PREF = 0xFFFFFFFF; + private static final int BROADCAST_CODE_SIZE = 16; + + /** + * Constructor to create an Empty object of {@link BluetoothLeBroadcastSourceInfo } with the + * given Source Id. + * + * <p>This is mainly used to represent the Empty Broadcast Source entries + * + * @param sourceId Source Id for this Broadcast Source info object + * @hide + */ + public BluetoothLeBroadcastSourceInfo(byte sourceId) { + mSourceId = sourceId; + mSourceAddressType = LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID; + mSourceDevice = null; + mSourceAdvSid = (byte) 0x00; + mBroadcastId = INVALID_BROADCAST_ID; + mPaSyncState = LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID; + mAudioSyncState = LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID; + mEncryptionStatus = LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID; + mBadBroadcastCode = null; + mNumSubGroups = 0; + mBroadcastCode = null; + } + + /*package*/ BluetoothLeBroadcastSourceInfo( + byte sourceId, + @LeAudioBroadcastSourceAddressType int addressType, + @NonNull BluetoothDevice device, + byte advSid, + int broadcastId, + @LeAudioBroadcastSinkPaSyncState int paSyncstate, + @LeAudioBroadcastSinkEncryptionState int encryptionStatus, + @LeAudioBroadcastSinkAudioSyncState int audioSyncstate, + @Nullable byte[] badCode, + byte numSubGroups, + @NonNull Map<Integer, Integer> bisSyncState, + @Nullable Map<Integer, byte[]> subgroupMetadata, + @NonNull String broadcastCode) { + mSourceId = sourceId; + mSourceAddressType = addressType; + mSourceDevice = device; + mSourceAdvSid = advSid; + mBroadcastId = broadcastId; + mPaSyncState = paSyncstate; + mEncryptionStatus = encryptionStatus; + mAudioSyncState = audioSyncstate; + + if (badCode != null && badCode.length != 0) { + mBadBroadcastCode = new byte[badCode.length]; + System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length); + } + mNumSubGroups = numSubGroups; + mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); + mSubgroupMetadata = new HashMap<Integer, byte[]>(subgroupMetadata); + mBroadcastCode = broadcastCode; + } + + @Override + public boolean equals(Object o) { + if (o instanceof BluetoothLeBroadcastSourceInfo) { + BluetoothLeBroadcastSourceInfo other = (BluetoothLeBroadcastSourceInfo) o; + return (other.mSourceId == mSourceId + && other.mSourceAddressType == mSourceAddressType + && other.mSourceDevice == mSourceDevice + && other.mSourceAdvSid == mSourceAdvSid + && other.mBroadcastId == mBroadcastId + && other.mPaSyncState == mPaSyncState + && other.mEncryptionStatus == mEncryptionStatus + && other.mAudioSyncState == mAudioSyncState + && Arrays.equals(other.mBadBroadcastCode, mBadBroadcastCode) + && other.mNumSubGroups == mNumSubGroups + && mSubgroupBisSyncState.equals(other.mSubgroupBisSyncState) + && mSubgroupMetadata.equals(other.mSubgroupMetadata) + && other.mBroadcastCode == mBroadcastCode); + } + return false; + } + + /** + * Checks if an instance of {@link BluetoothLeBroadcastSourceInfo} is empty. + * + * @hide + */ + public boolean isEmpty() { + boolean ret = false; + if (mSourceAddressType == LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID + && mSourceDevice == null + && mSourceAdvSid == (byte) 0 + && mPaSyncState == LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID + && mEncryptionStatus == LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID + && mAudioSyncState == LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID + && mBadBroadcastCode == null + && mNumSubGroups == 0 + && mSubgroupBisSyncState.size() == 0 + && mSubgroupMetadata.size() == 0 + && mBroadcastCode == null) { + ret = true; + } + return ret; + } + + /** + * Compares an instance of {@link BluetoothLeBroadcastSourceInfo} with the provided instance. + * + * @hide + */ + public boolean matches(BluetoothLeBroadcastSourceInfo srcInfo) { + boolean ret = false; + if (srcInfo == null) { + ret = false; + } else { + if (mSourceDevice == null) { + if (mSourceAdvSid == srcInfo.getAdvertisingSid() + && mSourceAddressType == srcInfo.getAdvAddressType()) { + ret = true; + } + } else { + if (mSourceDevice.equals(srcInfo.getSourceDevice()) + && mSourceAdvSid == srcInfo.getAdvertisingSid() + && mSourceAddressType == srcInfo.getAdvAddressType() + && mBroadcastId == srcInfo.getBroadcastId()) { + ret = true; + } + } + } + return ret; + } + + @Override + public int hashCode() { + return Objects.hash( + mSourceId, + mSourceAddressType, + mSourceDevice, + mSourceAdvSid, + mBroadcastId, + mPaSyncState, + mEncryptionStatus, + mAudioSyncState, + mBadBroadcastCode, + mNumSubGroups, + mSubgroupBisSyncState, + mSubgroupMetadata, + mBroadcastCode); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "{BluetoothLeBroadcastSourceInfo : mSourceId" + + mSourceId + + " addressType: " + + mSourceAddressType + + " sourceDevice: " + + mSourceDevice + + " mSourceAdvSid:" + + mSourceAdvSid + + " mBroadcastId:" + + mBroadcastId + + " mPaSyncState:" + + mPaSyncState + + " mEncryptionStatus:" + + mEncryptionStatus + + " mAudioSyncState:" + + mAudioSyncState + + " mBadBroadcastCode:" + + mBadBroadcastCode + + " mNumSubGroups:" + + mNumSubGroups + + " mSubgroupBisSyncState:" + + mSubgroupBisSyncState + + " mSubgroupMetadata:" + + mSubgroupMetadata + + " mBroadcastCode:" + + mBroadcastCode + + "}"; + } + + /** + * Get the Source Id + * + * @return byte representing the Source Id, {@link + * #LE_AUDIO_BROADCAST_ASSISTANT_INVALID_SOURCE_ID} if invalid + * @hide + */ + public byte getSourceId() { + return mSourceId; + } + + /** + * Set the Source Id + * + * @param sourceId source Id + * @hide + */ + public void setSourceId(byte sourceId) { + mSourceId = sourceId; + } + + /** + * Set the Broadcast Source device + * + * @param sourceDevice the Broadcast Source BluetoothDevice + * @hide + */ + public void setSourceDevice(@NonNull BluetoothDevice sourceDevice) { + mSourceDevice = sourceDevice; + } + + /** + * Get the Broadcast Source BluetoothDevice + * + * @return Broadcast Source BluetoothDevice + * @hide + */ + public @NonNull BluetoothDevice getSourceDevice() { + return mSourceDevice; + } + + /** + * Set the address type of the Broadcast Source advertisements + * + * @hide + */ + public void setAdvAddressType(@LeAudioBroadcastSourceAddressType int addressType) { + mSourceAddressType = addressType; + } + + /** + * Get the address type used by advertisements from the Broadcast Source. + * BluetoothLeBroadcastSourceInfo Object + * + * @hide + */ + @LeAudioBroadcastSourceAddressType + public int getAdvAddressType() { + return mSourceAddressType; + } + + /** + * Set the advertising SID of the Broadcast Source advertisement. + * + * @param advSid advertising SID of the Broadcast Source + * @hide + */ + public void setAdvertisingSid(byte advSid) { + mSourceAdvSid = advSid; + } + + /** + * Get the advertising SID of the Broadcast Source advertisement. + * + * @return advertising SID of the Broadcast Source + * @hide + */ + public byte getAdvertisingSid() { + return mSourceAdvSid; + } + + /** + * Get the Broadcast ID of the Broadcast Source. + * + * @return broadcast ID + * @hide + */ + public int getBroadcastId() { + return mBroadcastId; + } + + /** + * Set the Periodic Advertising (PA) Sync State. + * + * @hide + */ + /*package*/ void setPaSyncState(@LeAudioBroadcastSinkPaSyncState int paSyncState) { + mPaSyncState = paSyncState; + } + + /** + * Get the Periodic Advertising (PA) Sync State + * + * @hide + */ + public @LeAudioBroadcastSinkPaSyncState int getMetadataSyncState() { + return mPaSyncState; + } + + /** + * Set the audio sync state + * + * @hide + */ + /*package*/ void setAudioSyncState(@LeAudioBroadcastSinkAudioSyncState int audioSyncState) { + mAudioSyncState = audioSyncState; + } + + /** + * Get the audio sync state + * + * @hide + */ + public @LeAudioBroadcastSinkAudioSyncState int getAudioSyncState() { + return mAudioSyncState; + } + + /** + * Set the encryption status + * + * @hide + */ + /*package*/ void setEncryptionStatus( + @LeAudioBroadcastSinkEncryptionState int encryptionStatus) { + mEncryptionStatus = encryptionStatus; + } + + /** + * Get the encryption status + * + * @hide + */ + public @LeAudioBroadcastSinkEncryptionState int getEncryptionStatus() { + return mEncryptionStatus; + } + + /** + * Get the incorrect broadcast code that the Scan delegator used to decrypt the Broadcast Audio + * Stream and failed. + * + * <p>This code is valid only if {@link #getEncryptionStatus} returns {@link + * #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} + * + * @return byte array containing bad broadcast value, null if the current encryption status is + * not {@link #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} + * @hide + */ + public @Nullable byte[] getBadBroadcastCode() { + return mBadBroadcastCode; + } + + /** + * Get the number of subgroups. + * + * @return number of subgroups + * @hide + */ + public byte getNumberOfSubGroups() { + return mNumSubGroups; + } + + public @NonNull Map<Integer, Integer> getSubgroupBisSyncState() { + return mSubgroupBisSyncState; + } + + public void setSubgroupBisSyncState(@NonNull Map<Integer, Integer> bisSyncState) { + mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); + } + + /*package*/ void setBroadcastCode(@NonNull String broadcastCode) { + mBroadcastCode = broadcastCode; + } + + /** + * Get the broadcast code + * + * @return + * @hide + */ + public @NonNull String getBroadcastCode() { + return mBroadcastCode; + } + + /** + * Set the broadcast ID + * + * @param broadcastId broadcast ID of the Broadcast Source + * @hide + */ + public void setBroadcastId(int broadcastId) { + mBroadcastId = broadcastId; + } + + private void writeSubgroupBisSyncStateToParcel( + @NonNull Parcel dest, @NonNull Map<Integer, Integer> subgroupBisSyncState) { + dest.writeInt(subgroupBisSyncState.size()); + for (Map.Entry<Integer, Integer> entry : subgroupBisSyncState.entrySet()) { + dest.writeInt(entry.getKey()); + dest.writeInt(entry.getValue()); + } + } + + private static void readSubgroupBisSyncStateFromParcel( + @NonNull Parcel in, @NonNull Map<Integer, Integer> subgroupBisSyncState) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer value = in.readInt(); + subgroupBisSyncState.put(key, value); + } + } + + private void writeSubgroupMetadataToParcel( + @NonNull Parcel dest, @Nullable Map<Integer, byte[]> subgroupMetadata) { + if (subgroupMetadata == null) { + dest.writeInt(0); + return; + } + + dest.writeInt(subgroupMetadata.size()); + for (Map.Entry<Integer, byte[]> entry : subgroupMetadata.entrySet()) { + dest.writeInt(entry.getKey()); + byte[] metadata = entry.getValue(); + if (metadata != null) { + dest.writeInt(metadata.length); + dest.writeByteArray(metadata); + } + } + } + + private static void readSubgroupMetadataFromParcel( + @NonNull Parcel in, @NonNull Map<Integer, byte[]> subgroupMetadata) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer metaDataLen = in.readInt(); + byte[] metadata = null; + if (metaDataLen != 0) { + metadata = new byte[metaDataLen]; + in.readByteArray(metadata); + } + subgroupMetadata.put(key, metadata); + } + } + + public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastSourceInfo> CREATOR = + new Parcelable.Creator<BluetoothLeBroadcastSourceInfo>() { + public @NonNull BluetoothLeBroadcastSourceInfo createFromParcel( + @NonNull Parcel in) { + final byte sourceId = in.readByte(); + final int sourceAddressType = in.readInt(); + final BluetoothDevice sourceDevice = + in.readTypedObject(BluetoothDevice.CREATOR); + final byte sourceAdvSid = in.readByte(); + final int broadcastId = in.readInt(); + final int paSyncState = in.readInt(); + final int audioSyncState = in.readInt(); + final int encryptionStatus = in.readInt(); + final int badBroadcastLen = in.readInt(); + byte[] badBroadcastCode = null; + + if (badBroadcastLen > 0) { + badBroadcastCode = new byte[badBroadcastLen]; + in.readByteArray(badBroadcastCode); + } + final byte numSubGroups = in.readByte(); + final String broadcastCode = in.readString(); + Map<Integer, Integer> subgroupBisSyncState = new HashMap<Integer, Integer>(); + readSubgroupBisSyncStateFromParcel(in, subgroupBisSyncState); + Map<Integer, byte[]> subgroupMetadata = new HashMap<Integer, byte[]>(); + readSubgroupMetadataFromParcel(in, subgroupMetadata); + + BluetoothLeBroadcastSourceInfo srcInfo = + new BluetoothLeBroadcastSourceInfo( + sourceId, + sourceAddressType, + sourceDevice, + sourceAdvSid, + broadcastId, + paSyncState, + encryptionStatus, + audioSyncState, + badBroadcastCode, + numSubGroups, + subgroupBisSyncState, + subgroupMetadata, + broadcastCode); + return srcInfo; + } + + public @NonNull BluetoothLeBroadcastSourceInfo[] newArray(int size) { + return new BluetoothLeBroadcastSourceInfo[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeByte(mSourceId); + out.writeInt(mSourceAddressType); + out.writeTypedObject(mSourceDevice, 0); + out.writeByte(mSourceAdvSid); + out.writeInt(mBroadcastId); + out.writeInt(mPaSyncState); + out.writeInt(mAudioSyncState); + out.writeInt(mEncryptionStatus); + + if (mBadBroadcastCode != null) { + out.writeInt(mBadBroadcastCode.length); + out.writeByteArray(mBadBroadcastCode); + } else { + // zero indicates that there is no "bad broadcast code" + out.writeInt(0); + } + out.writeByte(mNumSubGroups); + out.writeString(mBroadcastCode); + writeSubgroupBisSyncStateToParcel(out, mSubgroupBisSyncState); + writeSubgroupMetadataToParcel(out, mSubgroupMetadata); + } + + private static void log(@NonNull String msg) { + if (DBG) { + Log.d(TAG, msg); + } + } +} +; diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java new file mode 100644 index 000000000000..fb7789db25c7 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeCall.java @@ -0,0 +1,285 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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.bluetooth; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelUuid; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.UUID; + +/** + * Representation of Call + * + * @hide + */ +public final class BluetoothLeCall implements Parcelable { + + /** @hide */ + @IntDef(prefix = "STATE_", value = { + STATE_INCOMING, + STATE_DIALING, + STATE_ALERTING, + STATE_ACTIVE, + STATE_LOCALLY_HELD, + STATE_REMOTELY_HELD, + STATE_LOCALLY_AND_REMOTELY_HELD + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State { + } + + /** + * A remote party is calling (incoming call). + * + * @hide + */ + public static final int STATE_INCOMING = 0x00; + + /** + * The process to call the remote party has started but the remote party is not + * being alerted (outgoing call). + * + * @hide + */ + public static final int STATE_DIALING = 0x01; + + /** + * A remote party is being alerted (outgoing call). + * + * @hide + */ + public static final int STATE_ALERTING = 0x02; + + /** + * The call is in an active conversation. + * + * @hide + */ + public static final int STATE_ACTIVE = 0x03; + + /** + * The call is connected but held locally. “Locally Held” implies that either + * the server or the client can affect the state. + * + * @hide + */ + public static final int STATE_LOCALLY_HELD = 0x04; + + /** + * The call is connected but held remotely. “Remotely Held” means that the state + * is controlled by the remote party of a call. + * + * @hide + */ + public static final int STATE_REMOTELY_HELD = 0x05; + + /** + * The call is connected but held both locally and remotely. + * + * @hide + */ + public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06; + + /** + * Whether the call direction is outgoing. + * + * @hide + */ + public static final int FLAG_OUTGOING_CALL = 0x00000001; + + /** + * Whether the call URI and Friendly Name are withheld by server. + * + * @hide + */ + public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002; + + /** + * Whether the call URI and Friendly Name are withheld by network. + * + * @hide + */ + public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004; + + /** Unique UUID that identifies this call */ + private UUID mUuid; + + /** Remote Caller URI */ + private String mUri; + + /** Caller friendly name */ + private String mFriendlyName; + + /** Call state */ + private @State int mState; + + /** Call flags */ + private int mCallFlags; + + /** @hide */ + public BluetoothLeCall(@NonNull BluetoothLeCall that) { + mUuid = new UUID(that.getUuid().getMostSignificantBits(), + that.getUuid().getLeastSignificantBits()); + mUri = that.mUri; + mFriendlyName = that.mFriendlyName; + mState = that.mState; + mCallFlags = that.mCallFlags; + } + + /** @hide */ + public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName, + @State int state, int callFlags) { + mUuid = uuid; + mUri = uri; + mFriendlyName = friendlyName; + mState = state; + mCallFlags = callFlags; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BluetoothLeCall that = (BluetoothLeCall) o; + return mUuid.equals(that.mUuid) && mUri.equals(that.mUri) + && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState + && mCallFlags == that.mCallFlags; + } + + @Override + public int hashCode() { + return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags); + } + + /** + * Returns a string representation of this BluetoothLeCall. + * + * <p> + * Currently this is the UUID. + * + * @return string representation of this BluetoothLeCall + */ + @Override + public String toString() { + return mUuid.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(new ParcelUuid(mUuid), 0); + out.writeString(mUri); + out.writeString(mFriendlyName); + out.writeInt(mState); + out.writeInt(mCallFlags); + } + + public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR = + new Parcelable.Creator<BluetoothLeCall>() { + public BluetoothLeCall createFromParcel(Parcel in) { + return new BluetoothLeCall(in); + } + + public BluetoothLeCall[] newArray(int size) { + return new BluetoothLeCall[size]; + } + }; + + private BluetoothLeCall(Parcel in) { + mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); + mUri = in.readString(); + mFriendlyName = in.readString(); + mState = in.readInt(); + mCallFlags = in.readInt(); + } + + /** + * Returns an UUID of this BluetoothLeCall. + * + * <p> + * An UUID is unique identifier of a BluetoothLeCall. + * + * @return UUID of this BluetoothLeCall + * @hide + */ + public @NonNull UUID getUuid() { + return mUuid; + } + + /** + * Returns a URI of the remote party of this BluetoothLeCall. + * + * @return string representation of this BluetoothLeCall + * @hide + */ + public @NonNull String getUri() { + return mUri; + } + + /** + * Returns a friendly name of the call. + * + * @return friendly name representation of this BluetoothLeCall + * @hide + */ + public @NonNull String getFriendlyName() { + return mFriendlyName; + } + + /** + * Returns the call state. + * + * @return the state of this BluetoothLeCall + * @hide + */ + public @State int getState() { + return mState; + } + + /** + * Returns the call flags. + * + * @return call flags + * @hide + */ + public int getCallFlags() { + return mCallFlags; + } + + /** + * Whether the call direction is incoming. + * + * @return true if incoming call, false otherwise + * @hide + */ + public boolean isIncomingCall() { + return (mCallFlags & FLAG_OUTGOING_CALL) == 0; + } +} diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java new file mode 100644 index 000000000000..5283e0804252 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeCallControl.java @@ -0,0 +1,911 @@ +/* + * Copyright 2019 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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.bluetooth; + +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.content.ComponentName; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; +import android.annotation.SuppressLint; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executor; + +/** + * This class provides the APIs to control the Call Control profile. + * + * <p> + * This class provides Bluetooth Telephone Bearer Service functionality, + * allowing applications to expose a GATT Service based interface to control the + * state of the calls by remote devices such as LE audio devices. + * + * <p> + * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the + * BluetoothLeCallControl proxy object. + * + * @hide + */ +public final class BluetoothLeCallControl implements BluetoothProfile { + private static final String TAG = "BluetoothLeCallControl"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** @hide */ + @IntDef(prefix = "RESULT_", value = { + RESULT_SUCCESS, + RESULT_ERROR_UNKNOWN_CALL_ID, + RESULT_ERROR_INVALID_URI, + RESULT_ERROR_APPLICATION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Result { + } + + /** + * Opcode write was successful. + * + * @hide + */ + public static final int RESULT_SUCCESS = 0; + + /** + * Unknown call Id has been used in the operation. + * + * @hide + */ + public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1; + + /** + * The URI provided in {@link Callback#onPlaceCallRequest} is invalid. + * + * @hide + */ + public static final int RESULT_ERROR_INVALID_URI = 2; + + /** + * Application internal error. + * + * @hide + */ + public static final int RESULT_ERROR_APPLICATION = 3; + + /** @hide */ + @IntDef(prefix = "TERMINATION_REASON_", value = { + TERMINATION_REASON_INVALID_URI, + TERMINATION_REASON_FAIL, + TERMINATION_REASON_REMOTE_HANGUP, + TERMINATION_REASON_SERVER_HANGUP, + TERMINATION_REASON_LINE_BUSY, + TERMINATION_REASON_NETWORK_CONGESTION, + TERMINATION_REASON_CLIENT_HANGUP, + TERMINATION_REASON_NO_SERVICE, + TERMINATION_REASON_NO_ANSWER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TerminationReason { + } + + /** + * Remote Caller ID value used to place a call was formed improperly. + * + * @hide + */ + public static final int TERMINATION_REASON_INVALID_URI = 0x00; + + /** + * Call fail. + * + * @hide + */ + public static final int TERMINATION_REASON_FAIL = 0x01; + + /** + * Remote party ended call. + * + * @hide + */ + public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02; + + /** + * Call ended from the server. + * + * @hide + */ + public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03; + + /** + * Line busy. + * + * @hide + */ + public static final int TERMINATION_REASON_LINE_BUSY = 0x04; + + /** + * Network congestion. + * + * @hide + */ + public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05; + + /** + * Client terminated. + * + * @hide + */ + public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06; + + /** + * No service. + * + * @hide + */ + public static final int TERMINATION_REASON_NO_SERVICE = 0x07; + + /** + * No answer. + * + * @hide + */ + public static final int TERMINATION_REASON_NO_ANSWER = 0x08; + + /* + * Flag indicating support for hold/unhold call feature. + * + * @hide + */ + public static final int CAPABILITY_HOLD_CALL = 0x00000001; + + /** + * Flag indicating support for joining calls feature. + * + * @hide + */ + public static final int CAPABILITY_JOIN_CALLS = 0x00000002; + + private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102; + private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103; + + private static final int REG_TIMEOUT = 10000; + + /** + * The template class is used to call callback functions on events from the TBS + * server. Callback functions are wrapped in this class and registered to the + * Android system during app registration. + * + * @hide + */ + public abstract static class Callback { + + private static final String TAG = "BluetoothLeCallControl.Callback"; + + /** + * Called when a remote client requested to accept the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to be accepted + * @hide + */ + public abstract void onAcceptCall(int requestId, @NonNull UUID callId); + + /** + * A remote client has requested to terminate the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to terminate + * @hide + */ + public abstract void onTerminateCall(int requestId, @NonNull UUID callId); + + /** + * A remote client has requested to hold the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to be put on hold + * @hide + */ + public void onHoldCall(int requestId, @NonNull UUID callId) { + Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); + } + + /** + * A remote client has requested to unhold the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to unhold + * @hide + */ + public void onUnholdCall(int requestId, @NonNull UUID callId) { + Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); + } + + /** + * A remote client has requested to place a call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The Id to be assigned for the new call + * @param uri The caller URI requested + * @hide + */ + public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); + + /** + * A remote client has requested to join the calls. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callIds The call Id list requested to join + * @hide + */ + public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { + Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); + } + } + + private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { + + private final Executor mExecutor; + private final Callback mCallback; + + CallbackWrapper(Executor executor, Callback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onBearerRegistered(int ccid) { + synchronized (mServerIfLock) { + if (mCallback != null) { + mCcid = ccid; + mServerIfLock.notifyAll(); + } else { + // registration timeout + Log.e(TAG, "onBearerRegistered: mCallback is null"); + } + } + } + + @Override + public void onAcceptCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onTerminateCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onHoldCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onUnholdCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { + List<UUID> uuids = new ArrayList<>(); + for (ParcelUuid parcelUuid : parcelUuids) { + uuids.add(parcelUuid.getUuid()); + } + + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids)); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + }; + + private Context mContext; + private ServiceListener mServiceListener; + private volatile IBluetoothLeCallControl mService; + private BluetoothAdapter mAdapter; + private int mCcid = 0; + private String mToken; + private Callback mCallback = null; + private Object mServerIfLock = new Object(); + + private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) + Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + doUnbind(); + } else { + doBind(); + } + } + }; + + /** + * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth + * telephone bearer service. + */ + /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) { + mContext = context; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mServiceListener = listener; + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + doBind(); + } + + private boolean doBind() { + synchronized (mConnection) { + if (mService == null) { + if (VDBG) + Log.d(TAG, "Binding service..."); + try { + return mAdapter.getBluetoothManager(). + bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, + mConnection); + } catch (RemoteException e) { + Log.e(TAG, "Unable to bind TelephoneBearerService", e); + } + } + } + return false; + } + + private void doUnbind() { + synchronized (mConnection) { + if (mService != null) { + if (VDBG) + Log.d(TAG, "Unbinding service..."); + try { + mAdapter.getBluetoothManager(). + unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, + mConnection); + } catch (RemoteException e) { + Log.e(TAG, "Unable to unbind TelephoneBearerService", e); + } finally { + mService = null; + } + } + } + } + + /* package */ void close() { + if (VDBG) + log("close()"); + unregisterBearer(); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException re) { + Log.e(TAG, "", re); + } + } + mServiceListener = null; + doUnbind(); + } + + private IBluetoothLeCallControl getService() { + return mService; + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public int getConnectionState(@Nullable BluetoothDevice device) { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public @NonNull List<BluetoothDevice> getConnectedDevices() { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( + @NonNull int[] states) { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Register Telephone Bearer exposing the interface that allows remote devices + * to track and control the call states. + * + * <p> + * This is an asynchronous call. The callback is used to notify success or + * failure if the function returns true. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * <!-- The UCI is a String identifier of the telephone bearer as defined at + * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers + * (login required). --> + * + * <!-- The examples of common URI schemes can be found in + * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> + * + * <!-- The Technology is an integer value. The possible values are defined at + * https://www.bluetooth.com/specifications/assigned-numbers (login required). + * --> + * + * @param uci Bearer Unique Client Identifier + * @param uriSchemes URI Schemes supported list + * @param capabilities bearer capabilities + * @param provider Network provider name + * @param technology Network technology + * @param executor {@link Executor} object on which callback will be + * executed. The Executor object is required. + * @param callback {@link Callback} object to which callback messages will + * be sent. The Callback object is required. + * @return true on success, false otherwise + * @hide + */ + @SuppressLint("ExecutorRegistration") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean registerBearer(@Nullable String uci, + @NonNull List<String> uriSchemes, int capabilities, + @NonNull String provider, int technology, + @NonNull Executor executor, @NonNull Callback callback) { + if (DBG) { + Log.d(TAG, "registerBearer"); + } + if (callback == null) { + throw new IllegalArgumentException("null parameter: " + callback); + } + if (mCcid != 0) { + return false; + } + + mToken = uci; + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + synchronized (mServerIfLock) { + if (mCallback != null) { + Log.e(TAG, "Bearer can be opened only once"); + return false; + } + + mCallback = callback; + try { + CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); + service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities, + provider, technology); + } catch (RemoteException e) { + Log.e(TAG, "", e); + mCallback = null; + return false; + } + + try { + mServerIfLock.wait(REG_TIMEOUT); + } catch (InterruptedException e) { + Log.e(TAG, "" + e); + mCallback = null; + } + + if (mCcid == 0) { + mCallback = null; + return false; + } + + return true; + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + + return false; + } + + /** + * Unregister Telephone Bearer Service and destroy all the associated data. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void unregisterBearer() { + if (DBG) { + Log.d(TAG, "unregisterBearer"); + } + if (mCcid == 0) { + return; + } + + int ccid = mCcid; + mCcid = 0; + mCallback = null; + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.unregisterBearer(mToken); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Get the Content Control ID (CCID) value. + * + * @return ccid Content Control ID value + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public int getContentControlId() { + return mCcid; + } + + /** + * Notify about the newly added call. + * + * <p> + * This shall be called as early as possible after the call has been added. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param call Newly added call + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallAdded(@NonNull BluetoothLeCall call) { + if (DBG) { + Log.d(TAG, "onCallAdded: call=" + call); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callAdded(mCcid, call); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Notify about the removed call. + * + * <p> + * This shall be called as early as possible after the call has been removed. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callId The Id of a call that has been removed + * @param reason Call termination reason + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { + if (DBG) { + Log.d(TAG, "callRemoved: callId=" + callId); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callRemoved(mCcid, new ParcelUuid(callId), reason); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Notify the call state change + * + * <p> + * This shall be called as early as possible after the state of the call has + * changed. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callId The call Id that state has been changed + * @param state Call state + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { + if (DBG) { + Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callStateChanged(mCcid, new ParcelUuid(callId), state); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Provide the current calls list + * + * <p> + * This function must be invoked after registration if application has any + * calls. + * + * @param calls current calls list + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.currentCallsList(mCcid, calls); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + } + + /** + * Provide the network current status + * + * <p> + * This function must be invoked on change of network state. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * <!-- The Technology is an integer value. The possible values are defined at + * https://www.bluetooth.com/specifications/assigned-numbers (login required). + * --> + * + * @param provider Network provider name + * @param technology Network technology + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void networkStateChanged(@NonNull String provider, int technology) { + if (DBG) { + Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.networkStateChanged(mCcid, provider, technology); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Send a response to a call control request to a remote device. + * + * <p> + * This function must be invoked in when a request is received by one of these + * callback methods: + * + * <ul> + * <li>{@link Callback#onAcceptCall} + * <li>{@link Callback#onTerminateCall} + * <li>{@link Callback#onHoldCall} + * <li>{@link Callback#onUnholdCall} + * <li>{@link Callback#onPlaceCall} + * <li>{@link Callback#onJoinCalls} + * </ul> + * + * @param requestId The ID of the request that was received with the callback + * @param result The result of the request to be sent to the remote devices + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void requestResult(int requestId, @Result int result) { + if (DBG) { + Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.requestResult(mCcid, requestId, result); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + } + + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + private static boolean isValidDevice(@Nullable BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); + } + + private static void log(String msg) { + Log.d(TAG, msg); + } + + private final IBluetoothProfileServiceConnection mConnection = + new IBluetoothProfileServiceConnection.Stub() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) { + Log.d(TAG, "Proxy object connected"); + } + mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service)); + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED)); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + if (DBG) { + Log.d(TAG, "Proxy object disconnected"); + } + doUnbind(); + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED)); + } + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_TBS_SERVICE_CONNECTED: { + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL, + BluetoothLeCallControl.this); + } + break; + } + case MESSAGE_TBS_SERVICE_DISCONNECTED: { + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL); + } + break; + } + } + } + }; +} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index e047e5d81a9d..d0f74e985729 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -240,12 +240,19 @@ public interface BluetoothProfile { int LE_AUDIO_BROADCAST = 26; /** + * @hide + * Telephone Bearer Service from Call Control Profile + * + */ + int LE_CALL_CONTROL = 27; + + /** * Max profile ID. This value should be updated whenever a new profile is added to match * the largest value assigned to a profile. * * @hide */ - int MAX_PROFILE_ID = 26; + int MAX_PROFILE_ID = 27; /** * Default priority for devices that we try to auto-connect to and diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index bb537ddab47b..2a8ff5185085 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -189,7 +189,7 @@ public final class BluetoothUuid { @NonNull @SystemApi public static final ParcelUuid CAP = - ParcelUuid.fromString("00008FE0-0000-1000-8000-00805F9B34FB"); + ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"); /** @hide */ @NonNull @SystemApi diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 3f02aa28ae05..78d5137ebfba 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -197,6 +197,20 @@ public final class AssociationInfo implements Parcelable { return macAddress.equals(mDeviceMacAddress); } + /** @hide */ + public @NonNull String toShortString() { + final StringBuilder sb = new StringBuilder(); + sb.append("id=").append(mId); + if (mDeviceMacAddress != null) { + sb.append(", addr=").append(getDeviceMacAddressAsString()); + } + if (mSelfManaged) { + sb.append(", self-managed"); + } + sb.append(", pkg=u").append(mUserId).append('/').append(mPackageName); + return sb.toString(); + } + @Override public String toString() { return "Association{" diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 117f30518311..bccfacf70842 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3615,9 +3615,8 @@ public abstract class Context { * {@link #BIND_ADJUST_WITH_ACTIVITY}. * @return {@code true} if the system is in the process of bringing up a * service that your client has permission to bind to; {@code false} - * if the system couldn't find the service. If this value is {@code true}, you - * should later call {@link #unbindService} to release the - * connection. + * if the system couldn't find the service. You should call {@link #unbindService} + * to release the connection even if this method returned {@code false}. * * @throws SecurityException if the client does not have the required permission to bind. */ @@ -5923,6 +5922,7 @@ public abstract class Context { * @see android.nearby.NearbyManager * @hide */ + @SystemApi public static final String NEARBY_SERVICE = "nearby"; /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2ff29cbdcc2a..af84392b5169 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3794,6 +3794,47 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.ACTION_IDLE_MAINTENANCE_END"; /** + * Broadcast Action: A broadcast sent by the system to indicate that + * {@link android.safetycenter.SafetyCenterManager} is requesting data from safety sources + * regarding their safety state. + * + * This broadcast is sent when a user triggers a data refresh from the Safety Center UI or when + * Safety Center detects that its stored safety information is stale and needs to be updated. + * + * This broadcast is sent explicitly to safety sources by targeting intents to a specified set + * of components provided by the safety sources in the safety source configuration. + * The receiving components should be manifest-declared receivers so that safety sources can be + * requested to send data even if they are not running. + * + * On receiving this broadcast, safety sources should determine their safety state + * according to the parameters specified in the intent extras (see below) and send Safety Center + * data about their safety state using + * {@link android.safetycenter.SafetyCenterManager#sendSafetyCenterUpdate(android.safetycenter.SafetySourceData)}. + * + * <p class="note">This is a protected intent that can only be sent by the system. + * + * <p>Includes the following extras: + * <ul> + * <li>{@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE}: An int representing the type of data + * being requested. Possible values are all values in {@link RefreshRequestType}. + * <li>{@link #EXTRA_REFRESH_SAFETY_SOURCE_IDS}: A {@code String[]} of ids + * representing the safety sources being requested for data. This extra exists for + * disambiguation in the case that a single component is responsible for receiving refresh + * requests for multiple safety sources. + * </ul> + * + * @hide + */ + // TODO(b/210805082): Define the term "safety sources" more concretely here once safety sources + // are configured in xml config. + // TODO(b/210979035): Determine recommendation for sources if they are requested for fresh data + // but cannot provide it. + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_REFRESH_SAFETY_SOURCES = + "android.intent.action.REFRESH_SAFETY_SOURCES"; + + /** * Broadcast Action: a remote intent is to be broadcasted. * * A remote intent is used for remote RPC between devices. The remote intent @@ -6386,6 +6427,77 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_VISIBILITY_ALLOW_LIST = "android.intent.extra.VISIBILITY_ALLOW_LIST"; + + /** + * Used as a {@code String[]} extra field in + * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES} intents to specify the safety + * source ids of the safety sources being requested for data by Safety Center. + * + * When this extra field is not specified in the intent, it is assumed that Safety Center is + * requesting data from all safety sources supported by the component receiving the broadcast. + * @hide + */ + @SystemApi + public static final String EXTRA_REFRESH_SAFETY_SOURCE_IDS = + "android.intent.extra.REFRESH_SAFETY_SOURCE_IDS"; + + /** + * Used as an {@code int} extra field in + * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES} intents to specify the type of + * data request from Safety Center. + * + * Possible values are all values in {@link RefreshRequestType}. + * + * @hide + */ + @SystemApi + public static final String EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE = + "android.intent.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE"; + + /** + * All possible types of data refresh requests in broadcasts with intent action + * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES}. + * + * @hide + */ + @IntDef(prefix = { "EXTRA_REFRESH_REQUEST_TYPE_" }, value = { + EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA, + EXTRA_REFRESH_REQUEST_TYPE_GET_DATA, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RefreshRequestType {} + + /** + * Used as an int value for + * {@link android.content.Intent#EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} + * to indicate that the safety source should fetch fresh data relating to their safety state + * upon receiving a broadcast with intent action + * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES} and provide it to Safety Center. + * + * The term "fresh" here means that the sources should ensure that the safety data is accurate + * as possible at the time of providing it to Safety Center, even if it involves performing an + * expensive and/or slow process. + * + * @hide + */ + @SystemApi + public static final int EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA = 0; + + /** + * Used as an int value for + * {@link android.content.Intent#EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} + * to indicate that upon receiving a broadcasts with intent action + * {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES}, the safety source should + * provide data relating to their safety state to Safety Center. + * + * If the source already has its safety data cached, it may provide it without triggering a + * process to fetch state which may be expensive and/or slow. + * + * @hide + */ + @SystemApi + public static final int EXTRA_REFRESH_REQUEST_TYPE_GET_DATA = 1; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 37fd3ffdeafa..cb8988eb5b92 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -38,6 +38,8 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.ParcelFileDescriptor; +import com.android.internal.infra.AndroidFuture; + import java.util.List; /** @@ -73,6 +75,8 @@ interface ILauncherApps { ParceledListSlice getShortcuts(String callingPackage, in ShortcutQueryWrapper query, in UserHandle user); + void getShortcutsAsync(String callingPackage, in ShortcutQueryWrapper query, + in UserHandle user, in AndroidFuture<List<ShortcutInfo>> cb); void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, in UserHandle user); boolean startShortcut(String callingPackage, String packageName, String featureId, String id, diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 617d3ab335e9..a0d348f1cbd5 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -69,6 +69,7 @@ import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileNotFoundException; @@ -84,6 +85,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; /** @@ -440,6 +442,17 @@ public class LauncherApps { public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2; /** + * Includes shortcuts from persistence layer in the search result. + * + * <p>The caller should make the query on a worker thread since accessing persistence layer + * is considered asynchronous. + * + * @hide + */ + @SystemApi + public static final int FLAG_GET_PERSISTED_DATA = 1 << 12; + + /** * Populate the persons field in the result. See {@link ShortcutInfo#getPersons()}. * * <p>The caller must have the system {@code ACCESS_SHORTCUTS} permission. @@ -459,6 +472,7 @@ public class LauncherApps { FLAG_MATCH_PINNED_BY_ANY_LAUNCHER, FLAG_GET_KEY_FIELDS_ONLY, FLAG_GET_PERSONS_DATA, + FLAG_GET_PERSISTED_DATA }) @Retention(RetentionPolicy.SOURCE) public @interface QueryFlags {} @@ -1137,6 +1151,9 @@ public class LauncherApps { @NonNull UserHandle user) { logErrorForInvalidProfileAccess(user); try { + if ((query.mQueryFlags & ShortcutQuery.FLAG_GET_PERSISTED_DATA) != 0) { + return getShortcutsBlocked(query, user); + } // Note this is the only case we need to update the disabled message for shortcuts // that weren't restored. // The restore problem messages are only shown by the user, and publishers will never @@ -1144,10 +1161,26 @@ public class LauncherApps { // changed callback, but that only returns shortcuts with the "key" information, so // that won't return disabled message. return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(), - new ShortcutQueryWrapper(query), user) - .getList()); + new ShortcutQueryWrapper(query), user) + .getList()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private List<ShortcutInfo> getShortcutsBlocked(@NonNull ShortcutQuery query, + @NonNull UserHandle user) { + logErrorForInvalidProfileAccess(user); + final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>(); + future.thenApply(this::maybeUpdateDisabledMessage); + try { + mService.getShortcutsAsync(mContext.getPackageName(), + new ShortcutQueryWrapper(query), user, future); + return future.get(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); } } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 251d5e8b6134..495100b0ae52 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2374,7 +2374,7 @@ public class PackageInstaller { STAGED_SESSION_UNKNOWN, STAGED_SESSION_CONFLICT}) @Retention(RetentionPolicy.SOURCE) - public @interface StagedSessionErrorCode{} + public @interface SessionErrorCode {} /** * Constant indicating that no error occurred during the preparation or the activation of * this staged session. @@ -2486,13 +2486,13 @@ public class PackageInstaller { public int[] childSessionIds = NO_SESSIONS; /** {@hide} */ - public boolean isStagedSessionApplied; + public boolean isSessionApplied; /** {@hide} */ - public boolean isStagedSessionReady; + public boolean isSessionReady; /** {@hide} */ - public boolean isStagedSessionFailed; - private int mStagedSessionErrorCode; - private String mStagedSessionErrorMessage; + public boolean isSessionFailed; + private int mSessionErrorCode; + private String mSessionErrorMessage; /** {@hide} */ public boolean isCommitted; @@ -2553,11 +2553,11 @@ public class PackageInstaller { if (childSessionIds == null) { childSessionIds = NO_SESSIONS; } - isStagedSessionApplied = source.readBoolean(); - isStagedSessionReady = source.readBoolean(); - isStagedSessionFailed = source.readBoolean(); - mStagedSessionErrorCode = source.readInt(); - mStagedSessionErrorMessage = source.readString(); + isSessionApplied = source.readBoolean(); + isSessionReady = source.readBoolean(); + isSessionFailed = source.readBoolean(); + mSessionErrorCode = source.readInt(); + mSessionErrorMessage = source.readString(); isCommitted = source.readBoolean(); rollbackDataPolicy = source.readInt(); createdMillis = source.readLong(); @@ -2951,7 +2951,7 @@ public class PackageInstaller { * since that is the one that should have been {@link Session#commit committed}. */ public boolean isStagedSessionActive() { - return isStaged && isCommitted && !isStagedSessionApplied && !isStagedSessionFailed + return isStaged && isCommitted && !isSessionApplied && !isSessionFailed && !hasParentSessionId(); } @@ -2992,7 +2992,7 @@ public class PackageInstaller { */ public boolean isStagedSessionApplied() { checkSessionIsStaged(); - return isStagedSessionApplied; + return isSessionApplied; } /** @@ -3001,7 +3001,7 @@ public class PackageInstaller { */ public boolean isStagedSessionReady() { checkSessionIsStaged(); - return isStagedSessionReady; + return isSessionReady; } /** @@ -3010,16 +3010,16 @@ public class PackageInstaller { */ public boolean isStagedSessionFailed() { checkSessionIsStaged(); - return isStagedSessionFailed; + return isSessionFailed; } /** * If something went wrong with a staged session, clients can check this error code to * understand which kind of failure happened. Only meaningful if {@code isStaged} is true. */ - public @StagedSessionErrorCode int getStagedSessionErrorCode() { + public @SessionErrorCode int getStagedSessionErrorCode() { checkSessionIsStaged(); - return mStagedSessionErrorCode; + return mSessionErrorCode; } /** @@ -3028,14 +3028,13 @@ public class PackageInstaller { */ public @NonNull String getStagedSessionErrorMessage() { checkSessionIsStaged(); - return mStagedSessionErrorMessage; + return mSessionErrorMessage; } /** {@hide} */ - public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode, - String errorMessage) { - mStagedSessionErrorCode = errorCode; - mStagedSessionErrorMessage = errorMessage; + public void setSessionErrorCode(@SessionErrorCode int errorCode, String errorMessage) { + mSessionErrorCode = errorCode; + mSessionErrorMessage = errorMessage; } /** @@ -3124,11 +3123,11 @@ public class PackageInstaller { dest.writeBoolean(forceQueryable); dest.writeInt(parentSessionId); dest.writeIntArray(childSessionIds); - dest.writeBoolean(isStagedSessionApplied); - dest.writeBoolean(isStagedSessionReady); - dest.writeBoolean(isStagedSessionFailed); - dest.writeInt(mStagedSessionErrorCode); - dest.writeString(mStagedSessionErrorMessage); + dest.writeBoolean(isSessionApplied); + dest.writeBoolean(isSessionReady); + dest.writeBoolean(isSessionFailed); + dest.writeInt(mSessionErrorCode); + dest.writeString(mSessionErrorMessage); dest.writeBoolean(isCommitted); dest.writeInt(rollbackDataPolicy); dest.writeLong(createdMillis); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9a7aeef69ed3..338dfd62f316 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -145,6 +145,20 @@ public abstract class PackageManager { "android.media.PROPERTY_MEDIA_CAPABILITIES"; /** + * Application level property that an app can specify to opt-out from having private data + * directories both on the internal and external storages. + * + * <p>Changing the value of this property during app update is not supported, and such updates + * will be rejected. + * + * <p>This should only be set by platform apps that know what they are doing. + * + * @hide + */ + public static final String PROPERTY_NO_APP_DATA_STORAGE = + "android.internal.PROPERTY_NO_APP_DATA_STORAGE"; + + /** * A property value set within the manifest. * <p> * The value of a property will only have a single type, as defined by diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 3ed5c6457fa5..087a7952acd7 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -29,6 +29,8 @@ import android.content.pm.LauncherApps.ShortcutQuery; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import com.android.internal.infra.AndroidFuture; + import java.util.List; /** @@ -50,6 +52,19 @@ public abstract class ShortcutServiceInternal { @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid); + /** + * Retrieves shortcuts asynchronously. Query will go through persistence layer (thus making the + * call async) if querying by shortcutIds in a specific package; otherwise it's effectively the + * same as calling {@link #getShortcuts}. + */ + public abstract void + getShortcutsAsync(int launcherUserId, + @NonNull String callingPackage, long changedSince, + @Nullable String packageName, @Nullable List<String> shortcutIds, + @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, + @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid, + AndroidFuture<List<ShortcutInfo>> cb); + public abstract boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String id, int userId); @@ -63,6 +78,14 @@ public abstract class ShortcutServiceInternal { @NonNull String packageName, @NonNull String shortcutId, int userId, int callingPid, int callingUid); + /** + * Retrieves the intents from a specified shortcut asynchronously. + */ + public abstract void createShortcutIntentsAsync( + int launcherUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + int callingPid, int callingUid, @NonNull AndroidFuture<Intent[]> cb); + public abstract void addListener(@NonNull ShortcutChangeListener listener); public abstract void addShortcutChangeCallback( @@ -82,6 +105,13 @@ public abstract class ShortcutServiceInternal { @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); + /** + * Retrieves a file descriptor from the icon in a specified shortcut asynchronously. + */ + public abstract void getShortcutIconFdAsync(int launcherUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull AndroidFuture<ParcelFileDescriptor> cb); + public abstract boolean hasShortcutHostPermission(int launcherUserId, @NonNull String callingPackage, int callingPid, int callingUid); @@ -117,6 +147,14 @@ public abstract class ShortcutServiceInternal { public abstract String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, @NonNull String packageName, @NonNull String shortcutId, int userId); + /** + * Retrieves the icon Uri of the shortcut asynchronously, and grants Uri read permission to the + * caller. + */ + public abstract void getShortcutIconUriAsync(int launcherUserId, + @NonNull String launcherPackage, @NonNull String packageName, + @NonNull String shortcutId, int userId, @NonNull AndroidFuture<String> cb); + public abstract boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, @NonNull IntentFilter filter); diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 26f0826fe860..77b8be351bd1 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -59,6 +59,72 @@ "include-filter": "android.content.pm.cts" } ] + }, + { + "name": "CtsUsesNativeLibraryTest", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + }, + { + "name": "CtsAppSearchHostTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + }, + { + "name": "CtsSilentUpdateHostTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + }, + { + "name": "CtsSuspendAppsTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + }, + { + "name": "CtsSecureFrpInstallTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + }, + { + "name": "CtsSuspendAppsPermissionTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] } ], "postsubmit": [ diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 16deaa05afce..f336672ffefa 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -21,6 +21,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED; @@ -934,6 +935,13 @@ public class ParsingPackageUtils { ); } + if (ParsedPermissionUtils.declareDuplicatePermission(pkg)) { + return input.error( + INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Declare duplicate permissions with different protection levels." + ); + } + convertCompatPermissions(pkg); convertSplitPermissions(pkg); diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java index 66e9d3ddca1f..86c8f02f9fd9 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java @@ -27,6 +27,7 @@ import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.util.ArrayMap; import android.util.Slog; import com.android.internal.R; @@ -34,6 +35,7 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.List; /** @hide */ public class ParsedPermissionUtils { @@ -271,4 +273,29 @@ public class ParsedPermissionUtils { } return size; } + + /** + * @return {@code true} if the package declares duplicate permissions with different + * protection levels. + */ + public static boolean declareDuplicatePermission(@NonNull ParsingPackage pkg) { + final List<ParsedPermission> permissions = pkg.getPermissions(); + final int size = permissions.size(); + if (size > 0) { + final ArrayMap<String, ParsedPermission> checkDuplicatePerm = new ArrayMap<>(size); + for (int i = 0; i < size; i++) { + final ParsedPermission parsedPermission = permissions.get(i); + final String name = parsedPermission.getName(); + final ParsedPermission perm = checkDuplicatePerm.get(name); + // Since a permission tree is also added as a permission with normal protection + // level, we need to skip if the parsedPermission is a permission tree. + if (perm != null && !(perm.isTree() || parsedPermission.isTree()) + && perm.getProtectionLevel() != parsedPermission.getProtectionLevel()) { + return true; + } + checkDuplicatePerm.put(name, parsedPermission); + } + } + return false; + } } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 282f1d343959..3a8513b9323f 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -464,7 +464,8 @@ public class SystemSensorManager extends SensorManager { IntentFilter filter = new IntentFilter("dynamic_sensor_change"); filter.addAction(Intent.ACTION_DYNAMIC_SENSOR_CHANGED); - mContext.registerReceiver(mDynamicSensorBroadcastReceiver, filter); + mContext.registerReceiver(mDynamicSensorBroadcastReceiver, filter, + Context.RECEIVER_NOT_EXPORTED); } } diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 01833fda5b95..e73116556758 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -965,7 +965,7 @@ public final class DisplayManagerGlobal { private static final class DisplayListenerDelegate extends Handler { public final DisplayListener mListener; - public long mEventsMask; + public volatile long mEventsMask; private final DisplayInfo mDisplayInfo = new DisplayInfo(); @@ -985,12 +985,12 @@ public final class DisplayManagerGlobal { removeCallbacksAndMessages(null); } - public synchronized void setEventsMask(@EventsMask long newEventsMask) { + public void setEventsMask(@EventsMask long newEventsMask) { mEventsMask = newEventsMask; } @Override - public synchronized void handleMessage(Message msg) { + public void handleMessage(Message msg) { switch (msg.what) { case EVENT_DISPLAY_ADDED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) { diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 22b444e663a3..afaa085c7cbd 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -51,7 +51,6 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; -import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1340,28 +1339,43 @@ public class InputMethodService extends AbstractInputMethodService { mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow"); - mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, - WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false); - mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars()); - mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM); - mWindow.getWindow().getAttributes().receiveInsetsIgnoringZOrder = true; - - // Automotive devices may request the navigation bar to be hidden when the IME shows up - // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the visible - // screen real estate. When this happens, the IME window should animate from the bottom of - // the screen to reduce the jank that happens from the lack of synchronization between the - // bottom system window and the IME window. - if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) { - mWindow.getWindow().setDecorFitsSystemWindows(false); - } - - // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set - // by default (but IME developers can opt this out later if they want a new behavior). - mWindow.getWindow().setFlags( - FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); + + { + final Window window = mWindow.getWindow(); + { + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.setTitle("InputMethod"); + lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD; + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = WindowManager.LayoutParams.WRAP_CONTENT; + lp.gravity = Gravity.BOTTOM; + lp.setFitInsetsTypes(statusBars() | navigationBars()); + lp.setFitInsetsSides(Side.all() & ~Side.BOTTOM); + lp.receiveInsetsIgnoringZOrder = true; + window.setAttributes(lp); + } + + // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set + // by default (but IME developers can opt this out later if they want a new behavior). + final int windowFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + final int windowFlagsMask = windowFlags + | WindowManager.LayoutParams.FLAG_DIM_BEHIND; // to be unset + window.setFlags(windowFlags, windowFlagsMask); + + // Automotive devices may request the navigation bar to be hidden when the IME shows up + // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the + // visible screen real estate. When this happens, the IME window should animate from the + // bottom of the screen to reduce the jank that happens from the lack of synchronization + // between the bottom system window and the IME window. + if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) { + window.setDecorFitsSystemWindows(false); + } + } initViews(); - mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); mInlineSuggestionSessionController = new InlineSuggestionSessionController( diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index c2286d17a414..6c8eb41d8724 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -17,11 +17,7 @@ package android.inputmethodservice; import static android.inputmethodservice.SoftInputWindowProto.BOUNDS; -import static android.inputmethodservice.SoftInputWindowProto.GRAVITY; -import static android.inputmethodservice.SoftInputWindowProto.NAME; -import static android.inputmethodservice.SoftInputWindowProto.TAKES_FOCUS; import static android.inputmethodservice.SoftInputWindowProto.WINDOW_STATE; -import static android.inputmethodservice.SoftInputWindowProto.WINDOW_TYPE; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -33,7 +29,6 @@ import android.os.Debug; import android.os.IBinder; import android.util.Log; import android.util.proto.ProtoOutputStream; -import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -42,23 +37,15 @@ import android.view.WindowManager; import java.lang.annotation.Retention; /** - * A SoftInputWindow is a Dialog that is intended to be used for a top-level input - * method window. It will be displayed along the edge of the screen, moving - * the application user interface away from it so that the focused item is - * always visible. - * @hide + * A {@link SoftInputWindow} is a {@link Dialog} that is intended to be used for a top-level input + * method window. It will be displayed along the edge of the screen, moving the application user + * interface away from it so that the focused item is always visible. */ -public final class SoftInputWindow extends Dialog { +final class SoftInputWindow extends Dialog { private static final boolean DEBUG = false; private static final String TAG = "SoftInputWindow"; - private final String mName; - private final Callback mCallback; - private final KeyEvent.Callback mKeyEventCallback; private final KeyEvent.DispatcherState mDispatcherState; - private final int mWindowType; - private final int mGravity; - private final boolean mTakesFocus; private final Rect mBounds = new Rect(); @Retention(SOURCE) @@ -93,22 +80,12 @@ public final class SoftInputWindow extends Dialog { private int mWindowState = WindowState.TOKEN_PENDING; /** - * Used to provide callbacks. - */ - public interface Callback { - /** - * Used to be notified when {@link Dialog#onBackPressed()} gets called. - */ - void onBackPressed(); - } - - /** * Set {@link IBinder} window token to the window. * * <p>This method can be called only once.</p> * @param token {@link IBinder} token to be associated with the window. */ - public void setToken(IBinder token) { + void setToken(IBinder token) { switch (mWindowState) { case WindowState.TOKEN_PENDING: // Normal scenario. Nothing to worry about. @@ -152,18 +129,9 @@ public final class SoftInputWindow extends Dialog { * using styles. This theme is applied on top of the current theme in * <var>context</var>. If 0, the default dialog theme will be used. */ - public SoftInputWindow(Context context, String name, int theme, Callback callback, - KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState, - int windowType, int gravity, boolean takesFocus) { + SoftInputWindow(Context context, int theme, KeyEvent.DispatcherState dispatcherState) { super(context, theme); - mName = name; - mCallback = callback; - mKeyEventCallback = keyEventCallback; mDispatcherState = dispatcherState; - mWindowType = windowType; - mGravity = gravity; - mTakesFocus = takesFocus; - initDockWindow(); } @Override @@ -188,83 +156,6 @@ public final class SoftInputWindow extends Dialog { } } - private void updateWidthHeight(WindowManager.LayoutParams lp) { - if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) { - lp.width = WindowManager.LayoutParams.MATCH_PARENT; - lp.height = WindowManager.LayoutParams.WRAP_CONTENT; - } else { - lp.width = WindowManager.LayoutParams.WRAP_CONTENT; - lp.height = WindowManager.LayoutParams.MATCH_PARENT; - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (mKeyEventCallback != null && mKeyEventCallback.onKeyDown(keyCode, event)) { - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyLongPress(int keyCode, KeyEvent event) { - if (mKeyEventCallback != null && mKeyEventCallback.onKeyLongPress(keyCode, event)) { - return true; - } - return super.onKeyLongPress(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (mKeyEventCallback != null && mKeyEventCallback.onKeyUp(keyCode, event)) { - return true; - } - return super.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { - if (mKeyEventCallback != null && mKeyEventCallback.onKeyMultiple(keyCode, count, event)) { - return true; - } - return super.onKeyMultiple(keyCode, count, event); - } - - @Override - public void onBackPressed() { - if (mCallback != null) { - mCallback.onBackPressed(); - } else { - super.onBackPressed(); - } - } - - private void initDockWindow() { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - - lp.type = mWindowType; - lp.setTitle(mName); - - lp.gravity = mGravity; - updateWidthHeight(lp); - - getWindow().setAttributes(lp); - - int windowSetFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; - int windowModFlags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_DIM_BEHIND; - - if (!mTakesFocus) { - windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - } else { - windowSetFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - windowModFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - } - - getWindow().setFlags(windowSetFlags, windowModFlags); - } - @Override public void show() { switch (mWindowState) { @@ -372,10 +263,6 @@ public final class SoftInputWindow extends Dialog { void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - proto.write(NAME, mName); - proto.write(WINDOW_TYPE, mWindowType); - proto.write(GRAVITY, mGravity); - proto.write(TAKES_FOCUS, mTakesFocus); mBounds.dumpDebug(proto, BOUNDS); proto.write(WINDOW_STATE, mWindowState); proto.end(token); diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index a9c52f1eddb6..596f4317dce3 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -339,7 +339,8 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { out.writeInt(template.getMatchRule()); BackupUtils.writeString(out, template.getSubscriberIds().iterator().next()); - BackupUtils.writeString(out, template.getWifiNetworkKey()); + BackupUtils.writeString(out, template.getWifiNetworkKeys().isEmpty() + ? null : template.getWifiNetworkKeys().iterator().next()); out.writeInt(template.getMeteredness()); return baos.toByteArray(); @@ -369,11 +370,13 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { try { final NetworkTemplate.Builder builder = new NetworkTemplate.Builder(matchRule) - .setWifiNetworkKey(wifiNetworkKey) .setMeteredness(metered); if (subscriberId != null) { builder.setSubscriberIds(Set.of(subscriberId)); } + if (wifiNetworkKey != null) { + builder.setWifiNetworkKeys(Set.of(wifiNetworkKey)); + } return builder.build(); } catch (IllegalArgumentException e) { throw new BackupUtils.BadVersionException( @@ -393,7 +396,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { case MATCH_MOBILE: return !template.getSubscriberIds().isEmpty(); case MATCH_WIFI: - if (Objects.equals(template.getWifiNetworkKey(), null) + if (template.getWifiNetworkKeys().isEmpty() && template.getSubscriberIds().isEmpty()) { return false; } diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index a23dae80625d..abe5f81088b1 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -135,7 +135,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private final List<UidBatteryConsumer> mUidBatteryConsumers; private final List<UserBatteryConsumer> mUserBatteryConsumers; private final AggregateBatteryConsumer[] mAggregateBatteryConsumers; - private final Parcel mHistoryBuffer; + private final BatteryStatsHistory mBatteryStatsHistory; private CursorWindow mBatteryConsumersCursorWindow; private BatteryUsageStats(@NonNull Builder builder) { @@ -146,7 +146,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { mDischargePercentage = builder.mDischargePercentage; mDischargedPowerLowerBound = builder.mDischargedPowerLowerBoundMah; mDischargedPowerUpperBound = builder.mDischargedPowerUpperBoundMah; - mHistoryBuffer = builder.mHistoryBuffer; + mBatteryStatsHistory = builder.mBatteryStatsHistory; mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs; mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs; mCustomPowerComponentNames = builder.mCustomPowerComponentNames; @@ -301,11 +301,11 @@ public final class BatteryUsageStats implements Parcelable, Closeable { */ @NonNull public BatteryStatsHistoryIterator iterateBatteryStatsHistory() { - if (mHistoryBuffer == null) { + if (mBatteryStatsHistory == null) { throw new IllegalStateException( "Battery history was not requested in the BatteryUsageStatsQuery"); } - return new BatteryStatsHistoryIterator(new BatteryStatsHistory(mHistoryBuffer)); + return new BatteryStatsHistoryIterator(mBatteryStatsHistory); } @Override @@ -363,12 +363,9 @@ public final class BatteryUsageStats implements Parcelable, Closeable { } if (source.readBoolean()) { - final byte[] historyBlob = source.readBlob(); - - mHistoryBuffer = Parcel.obtain(); - mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length); + mBatteryStatsHistory = BatteryStatsHistory.createFromBatteryUsageStatsParcel(source); } else { - mHistoryBuffer = null; + mBatteryStatsHistory = null; } } @@ -389,9 +386,9 @@ public final class BatteryUsageStats implements Parcelable, Closeable { mBatteryConsumersCursorWindow.writeToParcel(dest, flags); - if (mHistoryBuffer != null) { + if (mBatteryStatsHistory != null) { dest.writeBoolean(true); - dest.writeBlob(mHistoryBuffer.marshall()); + mBatteryStatsHistory.writeToBatteryUsageStatsParcel(dest); } else { dest.writeBoolean(false); } @@ -770,7 +767,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { new SparseArray<>(); private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders = new SparseArray<>(); - private Parcel mHistoryBuffer; + private BatteryStatsHistory mBatteryStatsHistory; public Builder(@NonNull String[] customPowerComponentNames) { this(customPowerComponentNames, false, false); @@ -895,8 +892,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable { * Sets the parceled recent history. */ @NonNull - public Builder setBatteryHistory(Parcel historyBuffer) { - mHistoryBuffer = historyBuffer; + public Builder setBatteryHistory(BatteryStatsHistory batteryStatsHistory) { + mBatteryStatsHistory = batteryStatsHistory; return this; } diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 1e424d1194ae..5d9f2189df1b 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -10,9 +10,8 @@ per-file PowerManagerInternal.java = michaelwr@google.com, santoscordon@google.c # BatteryStats per-file *BatteryConsumer* = file:/BATTERY_STATS_OWNERS per-file BatteryManager* = file:/BATTERY_STATS_OWNERS -per-file BatteryStats* = file:/BATTERY_STATS_OWNERS -per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS per-file PowerComponents.java = file:/BATTERY_STATS_OWNERS +per-file *Stats* = file:/BATTERY_STATS_OWNERS # Multiuser per-file IUser* = file:/MULTIUSER_OWNERS diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index dbb5a2c5af63..70aaa5e52c44 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -15,10 +15,13 @@ */ package android.os; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ActivityManager; import android.app.ActivityThread; @@ -2690,6 +2693,12 @@ public final class StrictMode { ((AndroidBlockGuardPolicy) policy).onCustomSlowCall(name); } + /** @hide */ + @SystemApi(client = MODULE_LIBRARIES) + public static void noteUntaggedSocket() { + if (vmUntaggedSocketEnabled()) onUntaggedSocket(); + } + /** * For code to note that a resource was obtained using a type other than its defined type. This * is a no-op unless the current thread's {@link android.os.StrictMode.ThreadPolicy} has {@link diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index d8f63444dd1c..b9252d643810 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -311,7 +311,6 @@ public final class SystemClock { * time or throw. * * @throws DateTimeException when no accurate network time can be provided. - * @hide */ public static @NonNull Clock currentNetworkTimeClock() { return new SimpleClock(ZoneOffset.UTC) { diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 5e4057bd840d..29accb9b3187 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1525,10 +1525,11 @@ public class StorageManager { result = totalBytes * CACHE_RESERVE_PERCENT_LOW / 100; } else { // Else, linearly interpolate the amount of space to reserve - result = ((CACHE_RESERVE_PERCENT_HIGH - CACHE_RESERVE_PERCENT_LOW) - * (usableBytes - storageThresholdHighBytes) + CACHE_RESERVE_PERCENT_HIGH - * (storageThresholdHighBytes - storageThresholdLowBytes)) * totalBytes - / (100 * (storageThresholdHighBytes - storageThresholdLowBytes)); + double slope = (CACHE_RESERVE_PERCENT_HIGH - CACHE_RESERVE_PERCENT_LOW) * totalBytes + / (100.0 * (storageThresholdHighBytes - storageThresholdLowBytes)); + double intercept = totalBytes * CACHE_RESERVE_PERCENT_LOW / 100.0 + - storageThresholdLowBytes * slope; + result = Math.round(slope * usableBytes + intercept); } return result; } diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 66e1c5a93f16..0e32a78411dd 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -54,4 +54,6 @@ oneway interface IPermissionController { void getGroupOfPlatformPermission( in String permissionName, in AndroidFuture<String> callback); + void getUnusedAppCount( + in AndroidFuture callback); } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 00f9e45fa22e..a0788e70b247 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -69,6 +69,7 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.IntConsumer; /** * Interface for communicating with the permission controller. @@ -786,4 +787,34 @@ public final class PermissionControllerManager { } }, executor); } + + /** + * Get the number of unused, hibernating apps for the user. + * + * @param executor executor to run callback on + * @param callback callback for when result is generated + */ + public void getUnusedAppCount(@NonNull @CallbackExecutor Executor executor, + @NonNull IntConsumer callback) { + checkNotNull(executor); + checkNotNull(callback); + + mRemoteService.postAsync(service -> { + AndroidFuture<Integer> unusedAppCountResult = new AndroidFuture<>(); + service.getUnusedAppCount(unusedAppCountResult); + return unusedAppCountResult; + }).whenCompleteAsync((count, err) -> { + if (err != null) { + Log.e(TAG, "Error getting unused app count", err); + callback.accept(0); + } else { + final long token = Binder.clearCallingIdentity(); + try { + callback.accept((int) count); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } } diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 8854e270eed6..c9793032c7eb 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -340,6 +340,20 @@ public abstract class PermissionControllerService extends Service { throw new AbstractMethodError("Must be overridden in implementing class"); } + /** + * Get the count of unused, hibernating apps on the device. + * + * @param callback callback after count is retrieved + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION) + @NonNull + public void onGetUnusedAppCount(@NonNull IntConsumer callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + @Override public final @NonNull IBinder onBind(Intent intent) { return new IPermissionController.Stub() { @@ -618,6 +632,20 @@ public abstract class PermissionControllerService extends Service { callback.completeExceptionally(t); } } + + @Override + public void getUnusedAppCount(@NonNull AndroidFuture callback) { + try { + Objects.requireNonNull(callback); + + enforceSomePermissionsGrantedToCaller( + Manifest.permission.MANAGE_APP_HIBERNATION); + + PermissionControllerService.this.onGetUnusedAppCount(callback::complete); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } }; } } diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index e2f59082c1f7..61e48c587e1b 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -24,6 +24,8 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -101,6 +103,26 @@ public final class PermissionManager { */ public static final int PERMISSION_HARD_DENIED = 2; + /** + * Activity action: Launch UI to review permission decisions. + * <p> + * <strong>Important:</strong>You must protect the activity that handles this action with the + * {@link android.Manifest.permission#START_REVIEW_PERMISSION_DECISIONS} permission to ensure + * that only the system can launch this activity. The system will not launch activities that are + * not properly protected. + * <p> + * Input: Nothing. + * </p> + * <p> + * Output: Nothing. + * </p> + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS) + public static final String ACTION_REVIEW_PERMISSION_DECISIONS = + "android.permission.action.REVIEW_PERMISSION_DECISIONS"; + + /** @hide */ public static final String LOG_TAG_TRACE_GRANTS = "PermissionGrantTrace"; @@ -218,7 +240,8 @@ public final class PermissionManager { /** * Checks whether a given data access chain described by the given {@link AttributionSource} * has a given permission. Call this method if you are the datasource which would not blame you - * for access to the data since you are the data. + * for access to the data since you are the data. Use this API if you are the datasource of the + * protected state. * * <strong>NOTE:</strong> Use this method only for permission checks at the * point where you will deliver the permission protected data to clients. diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index f0e6624f3f4f..658e033d10ea 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -318,7 +318,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis String permGroup = usedPermGroups.get(permGroupNum); ArrayMap<OpUsage, CharSequence> usagesWithLabels = - getUniqueUsagesWithLabels(rawUsages.get(permGroup)); + getUniqueUsagesWithLabels(permGroup, rawUsages.get(permGroup)); if (permGroup.equals(OPSTR_PHONE_CALL_MICROPHONE)) { isPhone = true; @@ -439,7 +439,8 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis return ListFormatter.getInstance().format(labels); } - private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(List<OpUsage> usages) { + private ArrayMap<OpUsage, CharSequence> getUniqueUsagesWithLabels(String permGroup, + List<OpUsage> usages) { ArrayMap<OpUsage, CharSequence> usagesAndLabels = new ArrayMap<>(); if (usages == null || usages.isEmpty()) { @@ -474,7 +475,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis // If this usage has a proxy, but is not a proxy, it is the end of a chain. // TODO remove once camera converted if (!proxies.containsKey(usageAttr) && usage.proxy != null - && !usage.op.equals(OPSTR_RECORD_AUDIO)) { + && !MICROPHONE.equals(permGroup)) { proxyLabels.put(usage, new ArrayList<>()); proxyPackages.add(usage.getPackageIdHash()); } @@ -546,7 +547,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis // TODO ntmyren: remove this proxy logic once camera is converted to AttributionSource // For now: don't add mic proxy usages - if (!start.op.equals(OPSTR_RECORD_AUDIO)) { + if (!MICROPHONE.equals(permGroup)) { usagesAndLabels.put(start, proxyLabelList.isEmpty() ? null : formatLabelList(proxyLabelList)); } @@ -560,7 +561,8 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis // if the list is empty or incomplete, do not show it. if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd() || !usageList.get(0).isStart() - || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) { + || !permGroup.equals(getGroupForOp(usageList.get(0).usage.op)) + || !MICROPHONE.equals(permGroup)) { continue; } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 22b95787cfde..6349cde2b8fc 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -564,6 +564,14 @@ public final class DeviceConfig { public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot"; /** + * Definitions for selection toolbar related functions. + * + * @hide + */ + @TestApi + public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar"; + + /** * List of namespaces which can be read without READ_DEVICE_CONFIG permission * * @hide @@ -571,7 +579,7 @@ public final class DeviceConfig { @NonNull private static final List<String> PUBLIC_NAMESPACES = Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA, - NAMESPACE_STATSD_JAVA_BOOT); + NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR); /** * Privacy related properties definitions. * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f5777ed0c8b3..6c045442fe4a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -391,6 +391,21 @@ public final class Settings { "android.settings.REDUCE_BRIGHT_COLORS_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of Color correction. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_COLOR_CORRECTION_SETTINGS = + "com.android.settings.ACCESSIBILITY_COLOR_SPACE_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of Color inversion. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -950,6 +965,19 @@ public final class Settings { "android.settings.LOCALE_SETTINGS"; /** + * Activity Action: Show settings to allow configuration of per application locale. + * <p> + * Input: The Intent's data URI can specify the application package name to directly invoke the + * app locale details GUI specific to the package name. + * For example "package:com.my.app". + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APP_LOCALE_SETTINGS = + "android.settings.APP_LOCALE_SETTINGS"; + + /** * Activity Action: Show settings to allow configuration of lockscreen. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -6495,6 +6523,27 @@ public final class Settings { public static final String ALLOW_MOCK_LOCATION = "mock_location"; /** + * This is used by Bluetooth Manager to store adapter name + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.S) + public static final String BLUETOOTH_NAME = "bluetooth_name"; + + /** + * This is used by Bluetooth Manager to store adapter address + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.S) + public static final String BLUETOOTH_ADDRESS = "bluetooth_address"; + + /** + * This is used by Bluetooth Manager to store whether adapter address is valid + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.S) + public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid"; + + /** * Setting to indicate that on device captions are enabled. * * @hide @@ -6503,6 +6552,16 @@ public final class Settings { @Readable public static final String ODI_CAPTIONS_ENABLED = "odi_captions_enabled"; + + /** + * Setting to indicate live caption button show or hide in the volume + * rocker. + * + * @hide + */ + public static final String ODI_CAPTIONS_VOLUME_UI_ENABLED = + "odi_captions_volume_ui_enabled"; + /** * On Android 8.0 (API level 26) and higher versions of the platform, * a 64-bit number (expressed as a hexadecimal string), unique to @@ -15157,7 +15216,10 @@ public final class Settings { * {@code p1[url_bar]:p2:p3[url_foo,url_bas]} * * @hide + * @deprecated Use {@link android.view.autofill.AutofillManager + * #DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES} instead. */ + @Deprecated @SystemApi @Readable public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = @@ -17037,6 +17099,15 @@ public final class Settings { */ public static final String CLOCKWORK_SYSUI_MAIN_ACTIVITY = "clockwork_sysui_main_activity"; + + /** + * Setting to disable power button long press launching Assistant. It's boolean, i.e. + * enabled = 1, disabled = 0. By default, this setting is enabled. + * + * @hide + */ + public static final String CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED = + "clockwork_long_press_to_assistant_enabled"; } } diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index e3c396976f6c..34e35d4512b9 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -3589,7 +3589,7 @@ public final class Telephony { * @hide */ public static final Uri PREFERRED_APN_URI = Uri.parse( - "content://telephony/carriers/preferapn/subId/"); + "content://telephony/carriers/preferapn/subId"); /** * The {@code content://} style URL for the perferred APN set id. @@ -3597,8 +3597,15 @@ public final class Telephony { * @hide */ public static final Uri PREFERRED_APN_SET_URI = Uri.parse( - "content://telephony/carriers/preferapnset/subId/"); + "content://telephony/carriers/preferapnset/subId"); + /** + * The id of preferred APN. + * + * @see #PREFERRED_APN_URI + * @hide + */ + public static final String APN_ID = "apn_id"; /** * The column name for ENFORCE_MANAGED_URI, indicates whether DPC-owned APNs are enforced. diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index c1a5636b7b34..ae323226d9cc 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -585,8 +585,10 @@ public final class DisplayCutout { * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional * area on the display. * - * There will be at most one non-functional area per short edge of the device, and none on - * the long edges. + * There will be at most one non-functional area per edge of the device. + * + * <p>Note that there is no bounding rectangle for waterfall cutout since it just represents the + * curved areas of the display but not the non-functional areas.</p> * * @return a list of bounding {@code Rect}s, one for each display cutout area. No empty Rect is * returned. @@ -607,8 +609,10 @@ public final class DisplayCutout { * functional area on the display. Ordinal value of BoundPosition is used as an index of * the array. * - * There will be at most one non-functional area per short edge of the device, and none on - * the long edges. + * There will be at most one non-functional area per edge of the device. + * + * <p>Note that there is no bounding rectangle for waterfall cutout since it just represents the + * curved areas of the display but not the non-functional areas.</p> * * @return an array of bounding {@code Rect}s, one for each display cutout area. This might * contain ZERO_RECT, which means there is no cutout area at the position. diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java new file mode 100644 index 000000000000..8524ac846d1a --- /dev/null +++ b/core/java/android/view/HandwritingInitiator.java @@ -0,0 +1,303 @@ +/* + * 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.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.ref.WeakReference; + +/** + * Initiates handwriting mode once it detects stylus movement in handwritable areas. + * + * It is designed to be used by {@link ViewRootImpl}. For every stylus related MotionEvent that is + * dispatched to view tree, ViewRootImpl should call {@link #onTouchEvent} method of this class. + * And it will automatically request to enter the handwriting mode when the conditions meet. + * + * Notice that ViewRootImpl should still dispatch MotionEvents to view tree as usual. + * And if it successfully enters the handwriting mode, the ongoing MotionEvent stream will be + * routed to the input method. Input system will fabricate an ACTION_CANCEL and send to + * ViewRootImpl. + * + * This class does nothing if: + * a) MotionEvents are not from stylus. + * b) The user taps or long-clicks with a stylus etc. + * c) Stylus pointer down position is not within a handwritable area. + * + * Used by InputMethodManager. + * @hide + */ +public class HandwritingInitiator { + /** + * The touchSlop from {@link ViewConfiguration} used to decide whether a pointer is considered + * moving or stationary. + */ + private final int mTouchSlop; + /** + * The timeout used to distinguish tap from handwriting. If the stylus doesn't move before this + * timeout, it's not considered as handwriting. + */ + private final long mTapTimeoutInMillis; + + private State mState = new State(); + + /** + * Helper method to reset the internal state of this class. + * Calling this method will also prevent the following MotionEvents + * triggers handwriting until the next stylus ACTION_DOWN/ACTION_POINTER_DOWN + * arrives. + */ + private void reset() { + mState = new State(); + } + + /** The reference to the View that currently has the input connection. */ + @Nullable + @VisibleForTesting + public WeakReference<View> mConnectedView = null; + + /** The editor bound reported by the connected View. */ + @Nullable + @VisibleForTesting + public Rect mEditorBound = null; + + /** + * When InputConnection restarts for a View, View#onInputConnectionCreatedInternal + * might be called before View#onInputConnectionClosedInternal, so we need to count the input + * connections and only set mConnectedView to null when mConnectionCount is zero. + */ + private int mConnectionCount = 0; + private final InputMethodManager mImm; + + @VisibleForTesting + public HandwritingInitiator(ViewConfiguration viewConfiguration, + InputMethodManager inputMethodManager) { + mTouchSlop = viewConfiguration.getScaledTouchSlop(); + mTapTimeoutInMillis = ViewConfiguration.getTapTimeout(); + mImm = inputMethodManager; + } + + /** + * Notify the HandwritingInitiator that a new MotionEvent has arrived. + * This method is non-block, and the event passed to this method should be dispatched to the + * View tree as usual. If HandwritingInitiator triggers the handwriting mode, an fabricated + * ACTION_CANCEL event will be sent to the ViewRootImpl. + * @param motionEvent the stylus MotionEvent. + */ + @VisibleForTesting + public void onTouchEvent(MotionEvent motionEvent) { + final int maskedAction = motionEvent.getActionMasked(); + switch (maskedAction) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + final int actionIndex = motionEvent.getActionIndex(); + final int toolType = motionEvent.getToolType(actionIndex); + // TOOL_TYPE_ERASER is also from stylus. This indicates that the user is holding + // the eraser button during handwriting. + if (toolType != MotionEvent.TOOL_TYPE_STYLUS + && toolType != MotionEvent.TOOL_TYPE_ERASER) { + // The motion event is not from a stylus event, ignore it. + return; + } + mState.mStylusPointerId = motionEvent.getPointerId(actionIndex); + mState.mStylusDownTimeInMillis = motionEvent.getEventTime(); + mState.mStylusDownX = motionEvent.getX(actionIndex); + mState.mStylusDownY = motionEvent.getY(actionIndex); + mState.mShouldInitHandwriting = true; + mState.mExceedTouchSlop = false; + break; + case MotionEvent.ACTION_POINTER_UP: + final int pointerId = motionEvent.getPointerId(motionEvent.getActionIndex()); + if (pointerId != mState.mStylusPointerId) { + // ACTION_POINTER_UP is from another stylus pointer, ignore the event. + return; + } + // Deliberately fall through. + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + // If it's ACTION_CANCEL or ACTION_UP, all the pointers go up. There is no need to + // check whether the stylus we are tracking goes up. + reset(); + break; + case MotionEvent.ACTION_MOVE: + // Either we've already tried to initiate handwriting, or the ongoing MotionEvent + // sequence is considered to be tap, long-click or other gestures. + if (!mState.mShouldInitHandwriting || mState.mExceedTouchSlop) { + return; + } + + final long timeElapsed = + motionEvent.getEventTime() - mState.mStylusDownTimeInMillis; + if (timeElapsed > mTapTimeoutInMillis) { + reset(); + return; + } + + final int pointerIndex = motionEvent.findPointerIndex(mState.mStylusPointerId); + final float x = motionEvent.getX(pointerIndex); + final float y = motionEvent.getY(pointerIndex); + if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) { + mState.mExceedTouchSlop = true; + tryStartHandwriting(); + } + } + } + + private View getConnectedView() { + if (mConnectedView == null) return null; + return mConnectedView.get(); + } + + /** + * Notify HandwritingInitiator that a new InputConnection is created. + * The caller of this method should guarantee that each onInputConnectionCreated call + * is paired with a onInputConnectionClosed call. + * @param view the view that created the current InputConnection. + * @see #onInputConnectionClosed(View) + */ + public void onInputConnectionCreated(@NonNull View view, @NonNull EditorInfo editorInfo) { + final View connectedView = getConnectedView(); +// updateEditorBound(editorInfo.getInitialEditorBound()); + if (connectedView == view) { + ++mConnectionCount; + } else { + mConnectedView = new WeakReference<>(view); + mConnectionCount = 1; + tryStartHandwriting(); + } + } + + /** + * Notify HandwritingInitiator that the InputConnection has closed for the given view. + * The caller of this method should guarantee that each onInputConnectionClosed call + * is paired with a onInputConnectionCreated call. + * @param view the view that closed the InputConnection. + */ + public void onInputConnectionClosed(@NonNull View view) { + final View connectedView = getConnectedView(); + if (connectedView == view) { + --mConnectionCount; + if (mConnectionCount == 0) { + mConnectedView = null; + mEditorBound = null; + } + } else { + // Unexpected branch, set mConnectedView to null to avoid further problem. + mConnectedView = null; + mEditorBound = null; + mConnectionCount = 0; + } + } + + /** + * Notify the HandwritingInitiator that editor bound of the connected view(the view with + * active InputConnection) has be updated. + * @param editorBound new the editor bounds of the connected view. + */ + public void updateEditorBound(@NonNull Rect editorBound) { + if (mEditorBound == null) { + mEditorBound = new Rect(editorBound); + } else { + mEditorBound.left = editorBound.left; + mEditorBound.top = editorBound.top; + mEditorBound.right = editorBound.right; + mEditorBound.bottom = editorBound.bottom; + } + } + + /** + * Try to initiate handwriting. For this method to successfully send startHandwriting signal, + * the following 3 conditions should meet: + * a) The stylus movement exceeds the touchSlop. + * b) A View has built InputConnection with IME. + * c) The stylus event lands into the connected View's boundary. + * This method will immediately fail without any side effect if condition a or b is not met. + * However, if both condition a and b are met but the condition c is not met, it will reset the + * internal states. And HandwritingInitiator won't attempt to call startHandwriting until the + * next ACTION_DOWN. + */ + private void tryStartHandwriting() { + if (!mState.mExceedTouchSlop) { + return; + } + final View connectedView = getConnectedView(); + if (connectedView == null || mEditorBound == null) { + return; + } + final ViewParent viewParent = connectedView.getParent(); + // Do a final check before startHandwriting. + if (viewParent != null && connectedView.isAttachedToWindow()) { + final Rect editorBounds = new Rect(mEditorBound); + if (viewParent.getChildVisibleRect(connectedView, editorBounds, null)) { + final int roundedInitX = Math.round(mState.mStylusDownX); + final int roundedInitY = Math.round(mState.mStylusDownY); + if (editorBounds.contains(roundedInitX, roundedInitY)) { + startHandwriting(mConnectedView.get()); + } + } + } + reset(); + } + + /** For test only. */ + @VisibleForTesting + public void startHandwriting(View view) { + // mImm.startHandwriting(view); + } + + private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) { + float dx = x1 - x2; + float dy = y1 - y2; + return dx * dx + dy * dy > mTouchSlop * mTouchSlop; + } + + /** Object that keeps the MotionEvent related states for HandwritingInitiator. */ + private static class State { + /** + * Whether it should initiate handwriting mode for the current MotionEvent sequence. + * (A series of MotionEvents from ACTION_DOWN to ACTION_UP) + * + * The purpose of this boolean value is: + * a) We should only request to start handwriting mode ONCE for each MotionEvent sequence. + * If we've already requested to enter handwriting mode for the ongoing MotionEvent + * sequence, this boolean is set to false. And it won't request to start handwriting again. + * + * b) If the MotionEvent sequence is considered to be tap, long-click or other gestures. + * This boolean will be set to false, and it won't request to start handwriting. + */ + private boolean mShouldInitHandwriting = false; + /** + * Whether the current ongoing stylus MotionEvent sequence already exceeds the touchSlop. + * It's used for the case where the stylus exceeds touchSlop before the target View built + * InputConnection. + */ + private boolean mExceedTouchSlop = false; + + /** The pointer id of the stylus pointer that is being tracked. */ + private int mStylusPointerId = -1; + /** The time stamp when the stylus pointer goes down. */ + private long mStylusDownTimeInMillis = -1; + /** The initial location where the stylus pointer goes down. */ + private float mStylusDownX = Float.NaN; + private float mStylusDownY = Float.NaN; + } +} diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 8db62f6553eb..adb8b86493d5 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1872,7 +1872,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { float x, float y, float pressure, float size, int metaState, float xPrecision, float yPrecision, int deviceId, int edgeFlags) { return obtain(downTime, eventTime, action, x, y, pressure, size, metaState, - xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_CLASS_POINTER, + xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_UNKNOWN, DEFAULT_DISPLAY); } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 3b5270960c99..ab33feae7c56 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -515,6 +515,15 @@ public final class SurfaceControl implements Parcelable { public static final int ENABLE_BACKPRESSURE = 0x00000100; /** + * Buffers from this SurfaceControl should be considered display decorations. + * + * If the hardware has optimizations for display decorations (e.g. rounded corners, camera + * cutouts, etc), it should use them for this layer. + * @hide + */ + public static final int DISPLAY_DECORATION = 0x00000200; + + /** * Surface creation flag: Creates a surface where color components are interpreted * as "non pre-multiplied" by their alpha channel. Of course this flag is * meaningless for surfaces without an alpha channel. By default @@ -3266,6 +3275,21 @@ public final class SurfaceControl implements Parcelable { } /** + * Sets whether the surface should take advantage of display decoration optimizations. + * @hide + */ + public Transaction setDisplayDecoration(SurfaceControl sc, boolean displayDecoration) { + checkPreconditions(sc); + if (displayDecoration) { + nativeSetFlags(mNativeObject, sc.mNativeObject, DISPLAY_DECORATION, + DISPLAY_DECORATION); + } else { + nativeSetFlags(mNativeObject, sc.mNativeObject, 0, DISPLAY_DECORATION); + } + return this; + } + + /** * Indicates whether the surface must be considered opaque, even if its pixel format is * set to translucent. This can be useful if an application needs full RGBA 8888 support * for instance but will still draw every pixel opaque. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 8fda48b1a36a..258359e98264 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -15272,7 +15272,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param event the KeyEvent object that defines the button action */ public boolean onKeyDown(int keyCode, KeyEvent event) { - if (KeyEvent.isConfirmKey(keyCode)) { + if (event.hasNoModifiers() && KeyEvent.isConfirmKey(keyCode)) { if ((mViewFlags & ENABLED_MASK) == DISABLED) { return true; } @@ -15329,7 +15329,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param event The KeyEvent object that defines the button action. */ public boolean onKeyUp(int keyCode, KeyEvent event) { - if (KeyEvent.isConfirmKey(keyCode)) { + if (event.hasNoModifiers() && KeyEvent.isConfirmKey(keyCode)) { if ((mViewFlags & ENABLED_MASK) == DISABLED) { return true; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1fdbf0e204da..77185116292e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -493,6 +493,9 @@ public final class ViewRootImpl implements ViewParent, protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo(); private final InputEventAssigner mInputEventAssigner = new InputEventAssigner(); + // Whether to draw this surface as DISPLAY_DECORATION. + boolean mDisplayDecorationCached = false; + /** * Update the Choreographer's FrameInfo object with the timing information for the current * ViewRootImpl instance. Erase the data in the current ViewFrameInfo to prepare for the next @@ -749,6 +752,17 @@ public final class ViewRootImpl implements ViewParent, int localChanges; } + private final HandwritingInitiator mHandwritingInitiator; + + /** + * Used by InputMethodManager. + * @hide + */ + @NonNull + public HandwritingInitiator getHandwritingInitiator() { + return mHandwritingInitiator; + } + /** * This is only used on the RenderThread when handling a blast sync. Specifically, it's only * used when calling {@link BLASTBufferQueue#setSyncTransaction(Transaction)} and then merged @@ -823,6 +837,8 @@ public final class ViewRootImpl implements ViewParent, ? Choreographer.getSfInstance() : Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this)); + mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration, + mContext.getSystemService(InputMethodManager.class)); String processorOverrideName = context.getResources().getString( R.string.config_inputEventCompatProcessorOverrideClassName); @@ -1970,26 +1986,29 @@ public final class ViewRootImpl implements ViewParent, return mBoundsLayer; } - Surface getOrCreateBLASTSurface() { + void updateBlastSurfaceIfNeeded() { if (!mSurfaceControl.isValid()) { - return null; + return; } - Surface ret = null; - if (mBlastBufferQueue == null) { - mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, - mSurfaceSize.x, mSurfaceSize.y, - mWindowAttributes.format); - // We only return the Surface the first time, as otherwise - // it hasn't changed and there is no need to update. - ret = mBlastBufferQueue.createSurface(); - } else { + if (mBlastBufferQueue != null && mBlastBufferQueue.isSameSurfaceControl(mSurfaceControl)) { mBlastBufferQueue.update(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); + return; } - return ret; + // If the SurfaceControl has been updated, destroy and recreate the BBQ to reset the BQ and + // BBQ states. + if (mBlastBufferQueue != null) { + mBlastBufferQueue.destroy(); + } + mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, + mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); + Surface blastSurface = mBlastBufferQueue.createSurface(); + // Only call transferFrom if the surface has changed to prevent inc the generation ID and + // causing EGL resources to be recreated. + mSurface.transferFrom(blastSurface); } private void setBoundsLayerCrop(Transaction t) { @@ -2853,6 +2872,12 @@ public final class ViewRootImpl implements ViewParent, if (mSurfaceControl.isValid()) { updateOpacity(mWindowAttributes, dragResizing, surfaceControlChanged /*forceUpdate */); + // No need to updateDisplayDecoration if it's a new SurfaceControl and + // mDisplayDecorationCached is false, since that's the default for a new + // SurfaceControl. + if (surfaceControlChanged && mDisplayDecorationCached) { + updateDisplayDecoration(); + } } if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString() @@ -6406,6 +6431,7 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; + mHandwritingInitiator.onTouchEvent(event); mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; @@ -7584,7 +7610,7 @@ public final class ViewRootImpl implements ViewParent, // When a new focused view is selected, we consume the navigation key because // navigation doesn't make much sense unless a view already has focus so // the key's purpose is to set focus. - if (isNavigationKey(event)) { + if (event.hasNoModifiers() && isNavigationKey(event)) { return ensureTouchMode(false); } @@ -7884,13 +7910,7 @@ public final class ViewRootImpl implements ViewParent, if (!useBLAST()) { mSurface.copyFrom(mSurfaceControl); } else { - final Surface blastSurface = getOrCreateBLASTSurface(); - // If blastSurface == null that means it hasn't changed since the last time we - // called. In this situation, avoid calling transferFrom as we would then - // inc the generation ID and cause EGL resources to be recreated. - if (blastSurface != null) { - mSurface.transferFrom(blastSurface); - } + updateBlastSurfaceIfNeeded(); } if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl); @@ -10440,6 +10460,23 @@ public final class ViewRootImpl implements ViewParent, } /** + * @hide + */ + public void setDisplayDecoration(boolean displayDecoration) { + if (displayDecoration == mDisplayDecorationCached) return; + + mDisplayDecorationCached = displayDecoration; + + if (mSurfaceControl.isValid()) { + updateDisplayDecoration(); + } + } + + private void updateDisplayDecoration() { + mTransaction.setDisplayDecoration(mSurfaceControl, mDisplayDecorationCached).apply(); + } + + /** * Sends a list of blur regions to SurfaceFlinger, tagged with a frame. * * @param regionCopy List of regions diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index aa6cb8308557..cd9f3eb65bc8 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -105,6 +105,7 @@ import android.graphics.Region; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.IInputConstants; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -120,6 +121,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -2755,7 +2757,7 @@ public interface WindowManager extends ViewManager { * * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag * is ignored unless there is a focused view that returns {@code true} from - * {@link View#isInEditMode()} when the window is focused.</p> + * {@link View#onCheckIsTextEditor()} when the window is focused.</p> */ public static final int SOFT_INPUT_STATE_VISIBLE = 4; @@ -2765,7 +2767,7 @@ public interface WindowManager extends ViewManager { * * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag * is ignored unless there is a focused view that returns {@code true} from - * {@link View#isInEditMode()} when the window is focused.</p> + * {@link View#onCheckIsTextEditor()} when the window is focused.</p> */ public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5; @@ -3325,21 +3327,13 @@ public interface WindowManager extends ViewManager { public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 3; /** - * When this window has focus, disable touch pad pointer gesture processing. - * The window will receive raw position updates from the touch pad instead - * of pointer movements and synthetic touch events. - * - * @hide - */ - public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001; - - /** * Does not construct an input channel for this window. The channel will therefore * be incapable of receiving input. * * @hide */ - public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002; + public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = + IInputConstants.InputFeature.NO_INPUT_CHANNEL; /** * When this window has focus, does not call user activity for all input events so @@ -3352,7 +3346,8 @@ public interface WindowManager extends ViewManager { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004; + public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = + IInputConstants.InputFeature.DISABLE_USER_ACTIVITY; /** * An input spy window. This window will receive all pointer events within its touchable @@ -3361,7 +3356,25 @@ public interface WindowManager extends ViewManager { * event's coordinates. * @hide */ - public static final int INPUT_FEATURE_SPY = 0x00000020; + public static final int INPUT_FEATURE_SPY = + IInputConstants.InputFeature.SPY; + + /** + * When used with the window flag {@link #FLAG_NOT_TOUCHABLE}, this window will continue + * to receive events from a stylus device within its touchable region. All other pointer + * events, such as from a mouse or touchscreen, will be dispatched to the windows behind it. + * + * This input feature has no effect when the window flag {@link #FLAG_NOT_TOUCHABLE} is + * not set. + * + * The window must be a trusted overlay to use this input feature. + * + * @see #FLAG_NOT_TOUCHABLE + * + * @hide + */ + public static final int INPUT_FEATURE_INTERCEPTS_STYLUS = + IInputConstants.InputFeature.INTERCEPTS_STYLUS; /** * An internal annotation for flags that can be specified to {@link #inputFeatures}. @@ -3370,18 +3383,20 @@ public interface WindowManager extends ViewManager { */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "INPUT_FEATURE_" }, value = { - INPUT_FEATURE_DISABLE_POINTER_GESTURES, INPUT_FEATURE_NO_INPUT_CHANNEL, INPUT_FEATURE_DISABLE_USER_ACTIVITY, + INPUT_FEATURE_SPY, + INPUT_FEATURE_INTERCEPTS_STYLUS, }) public @interface InputFeatureFlags {} /** * Control special features of the input subsystem. * - * @see #INPUT_FEATURE_DISABLE_POINTER_GESTURES * @see #INPUT_FEATURE_NO_INPUT_CHANNEL * @see #INPUT_FEATURE_DISABLE_USER_ACTIVITY + * @see #INPUT_FEATURE_SPY + * @see #INPUT_FEATURE_INTERCEPTS_STYLUS * @hide */ @InputFeatureFlags @@ -4485,7 +4500,7 @@ public interface WindowManager extends ViewManager { sb.append(hasSystemUiListeners); } if (inputFeatures != 0) { - sb.append(" if=").append(inputFeatureToString(inputFeatures)); + sb.append(" if=").append(inputFeaturesToString(inputFeatures)); } if (userActivityTimeout >= 0) { sb.append(" userActivityTimeout=").append(userActivityTimeout); @@ -4787,17 +4802,28 @@ public interface WindowManager extends ViewManager { } } - private static String inputFeatureToString(int inputFeature) { - switch (inputFeature) { - case INPUT_FEATURE_DISABLE_POINTER_GESTURES: - return "DISABLE_POINTER_GESTURES"; - case INPUT_FEATURE_NO_INPUT_CHANNEL: - return "NO_INPUT_CHANNEL"; - case INPUT_FEATURE_DISABLE_USER_ACTIVITY: - return "DISABLE_USER_ACTIVITY"; - default: - return Integer.toString(inputFeature); + private static String inputFeaturesToString(int inputFeatures) { + final List<String> features = new ArrayList<>(); + if ((inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) != 0) { + inputFeatures &= ~INPUT_FEATURE_NO_INPUT_CHANNEL; + features.add("INPUT_FEATURE_NO_INPUT_CHANNEL"); + } + if ((inputFeatures & INPUT_FEATURE_DISABLE_USER_ACTIVITY) != 0) { + inputFeatures &= ~INPUT_FEATURE_DISABLE_USER_ACTIVITY; + features.add("INPUT_FEATURE_DISABLE_USER_ACTIVITY"); + } + if ((inputFeatures & INPUT_FEATURE_SPY) != 0) { + inputFeatures &= ~INPUT_FEATURE_SPY; + features.add("INPUT_FEATURE_SPY"); + } + if ((inputFeatures & INPUT_FEATURE_INTERCEPTS_STYLUS) != 0) { + inputFeatures &= ~INPUT_FEATURE_INTERCEPTS_STYLUS; + features.add("INPUT_FEATURE_INTERCEPTS_STYLUS"); + } + if (inputFeatures != 0) { + features.add(Integer.toHexString(inputFeatures)); } + return String.join(" | ", features); } /** diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java index 91ef8a560013..e6385a5ecbd5 100644 --- a/core/java/android/view/accessibility/AccessibilityCache.java +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -46,6 +46,8 @@ public class AccessibilityCache { private static final boolean CHECK_INTEGRITY = Build.IS_ENG; + private boolean mEnabled = true; + /** * {@link AccessibilityEvent} types that are critical for the cache to stay up to date * @@ -98,6 +100,21 @@ public class AccessibilityCache { mAccessibilityNodeRefresher = nodeRefresher; } + /** Returns if the cache is enabled. */ + public boolean isEnabled() { + synchronized (mLock) { + return mEnabled; + } + } + + /** Sets enabled status. */ + public void setEnabled(boolean enabled) { + synchronized (mLock) { + mEnabled = enabled; + clear(); + } + } + /** * Sets all {@link AccessibilityWindowInfo}s of all displays into the cache. * The key of SparseArray is display ID. @@ -110,6 +127,12 @@ public class AccessibilityCache { SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays, long populationTimeStamp) { synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return; + } if (DEBUG) { Log.i(LOG_TAG, "Set windows"); } @@ -148,6 +171,12 @@ public class AccessibilityCache { */ public void addWindow(AccessibilityWindowInfo window) { synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return; + } if (DEBUG) { Log.i(LOG_TAG, "Caching window: " + window.getId() + " at display Id [ " + window.getDisplayId() + " ]"); @@ -177,6 +206,12 @@ public class AccessibilityCache { public void onAccessibilityEvent(AccessibilityEvent event) { AccessibilityNodeInfo nodeToRefresh = null; synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return; + } if (DEBUG) { Log.i(LOG_TAG, "onAccessibilityEvent(" + event + ")"); } @@ -292,6 +327,12 @@ public class AccessibilityCache { */ public AccessibilityNodeInfo getNode(int windowId, long accessibilityNodeId) { synchronized(mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return null; + } LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); if (nodes == null) { return null; @@ -309,6 +350,28 @@ public class AccessibilityCache { } } + /** Returns {@code true} if {@code info} is in the cache. */ + public boolean isNodeInCache(AccessibilityNodeInfo info) { + if (info == null) { + return false; + } + int windowId = info.getWindowId(); + long accessibilityNodeId = info.getSourceNodeId(); + synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return false; + } + LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); + if (nodes == null) { + return false; + } + return nodes.get(accessibilityNodeId) != null; + } + } + /** * Gets all {@link AccessibilityWindowInfo}s of all displays from the cache. * @@ -317,6 +380,12 @@ public class AccessibilityCache { */ public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() { synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return null; + } if (!mIsAllWindowsCached) { return null; } @@ -373,6 +442,12 @@ public class AccessibilityCache { */ public AccessibilityWindowInfo getWindow(int windowId) { synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return null; + } final int displayCounts = mWindowCacheByDisplay.size(); for (int i = 0; i < displayCounts; i++) { final SparseArray<AccessibilityWindowInfo> windowsOfDisplay = @@ -397,6 +472,12 @@ public class AccessibilityCache { */ public void add(AccessibilityNodeInfo info) { synchronized(mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return; + } if (VERBOSE) { Log.i(LOG_TAG, "add(" + info + ")"); } @@ -522,6 +603,12 @@ public class AccessibilityCache { */ public AccessibilityNodeInfo getFocus(int focusType, long initialNodeId, int windowId) { synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return null; + } int currentFocusWindowId; long currentFocusId; if (focusType == FOCUS_ACCESSIBILITY) { @@ -602,6 +689,23 @@ public class AccessibilityCache { mNodeCache.remove(windowId); } + /** Clears a subtree rooted at {@code info}. */ + public boolean clearSubTree(AccessibilityNodeInfo info) { + if (info == null) { + return false; + } + synchronized (mLock) { + if (!mEnabled) { + if (DEBUG) { + Log.i(LOG_TAG, "Cache is disabled"); + } + return false; + } + clearSubTreeLocked(info.getWindowId(), info.getSourceNodeId()); + return true; + } + } + /** * Clears a subtree rooted at the node with the given id that is * hosted in a given window. diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 83712b40c452..a427ab8fe837 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -24,7 +24,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; -import android.util.Pools.SynchronizedPool; import com.android.internal.util.BitUtils; @@ -806,10 +805,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static final int TYPES_ALL_MASK = 0xFFFFFFFF; - private static final int MAX_POOL_SIZE = 10; - private static final SynchronizedPool<AccessibilityEvent> sPool = - new SynchronizedPool<>(MAX_POOL_SIZE); - @UnsupportedAppUsage private @EventType int mEventType; private CharSequence mPackageName; @@ -1170,7 +1165,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static AccessibilityEvent obtainWindowsChangedEvent( int windowId, int windowChangeTypes) { - final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED); + final AccessibilityEvent event = new AccessibilityEvent(TYPE_WINDOWS_CHANGED); event.setWindowId(windowId); event.setWindowChanges(windowChangeTypes); event.setImportantForAccessibility(true); @@ -1178,69 +1173,58 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** - * Returns a cached instance if such is available or a new one is - * instantiated with its type property set. - * - * <p>In most situations object pooling is not beneficial. Create a new instance using the - * constructor {@link #AccessibilityEvent(int)} instead. + * Instantiates a new AccessibilityEvent instance with its type property set. * + * @deprecated Object pooling has been discontinued. Create a new instance using the + * constructor {@link #AccessibilityEvent()} instead. * @param eventType The event type. * @return An instance. */ + @Deprecated public static AccessibilityEvent obtain(int eventType) { - AccessibilityEvent event = AccessibilityEvent.obtain(); + AccessibilityEvent event = new AccessibilityEvent(); event.setEventType(eventType); return event; } /** - * Returns a cached instance if such is available or a new one is - * created. The returned instance is initialized from the given + * Instantiates a new AccessibilityEvent instance. + * The returned instance is initialized from the given * <code>event</code>. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the - * constructor {@link #AccessibilityEvent(AccessibilityEvent)} instead. - * + * @deprecated Object pooling has been discontinued. Create a new instance using the + * constructor {@link #AccessibilityEvent()} instead. * @param event The other event. * @return An instance. */ + @Deprecated public static AccessibilityEvent obtain(AccessibilityEvent event) { - AccessibilityEvent eventClone = AccessibilityEvent.obtain(); + AccessibilityEvent eventClone = new AccessibilityEvent(); eventClone.init(event); return eventClone; } /** - * Returns a cached instance if such is available or a new one is - * instantiated. + * Instantiates a new AccessibilityEvent instance. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link #AccessibilityEvent()} instead. - * * @return An instance. */ + @Deprecated public static AccessibilityEvent obtain() { - AccessibilityEvent event = sPool.acquire(); - if (event == null) event = new AccessibilityEvent(); - if (DEBUG_ORIGIN) event.originStackTrace = Thread.currentThread().getStackTrace(); - return event; + return new AccessibilityEvent(); } /** - * Recycles an instance back to be reused. - * <p> - * <b>Note: You must not touch the object after calling this function.</b> - * </p> + * Previously would recycle an instance back to be reused. * - * <p>In most situations object pooling is not beneficial, and recycling is not necessary. - * - * @throws IllegalStateException If the event is already recycled. + * @deprecated Object pooling has been discontinued. Calling this function now will have + * no effect. */ @Override - public void recycle() { - clear(); - sPool.release(this); - } + @Deprecated + public void recycle() {} /** * Clears the state of this instance. @@ -1260,7 +1244,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par if (mRecords != null) { while (!mRecords.isEmpty()) { AccessibilityRecord record = mRecords.remove(0); - record.recycle(); } } if (DEBUG_ORIGIN) originStackTrace = null; @@ -1288,7 +1271,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par if (recordCount > 0) { mRecords = new ArrayList<>(recordCount); for (int i = 0; i < recordCount; i++) { - AccessibilityRecord record = AccessibilityRecord.obtain(); + AccessibilityRecord record = new AccessibilityRecord(); readAccessibilityRecordFromParcel(record, parcel); record.mConnectionId = mConnectionId; mRecords.add(record); @@ -1527,7 +1510,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityEvent> CREATOR = new Parcelable.Creator<AccessibilityEvent>() { public AccessibilityEvent createFromParcel(Parcel parcel) { - AccessibilityEvent event = AccessibilityEvent.obtain(); + AccessibilityEvent event = new AccessibilityEvent(); event.initFromParcel(parcel); return event; } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index dc4c59a1e1ec..6f4bc719dc84 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -18,6 +18,7 @@ package android.view.accessibility; import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT; import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK; +import static android.os.Build.VERSION_CODES.S; import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; @@ -225,6 +226,9 @@ public final class AccessibilityInteractionClient */ public static void addConnection(int connectionId, IAccessibilityServiceConnection connection, boolean initializeCache) { + if (connectionId == NO_ID) { + return; + } synchronized (sConnectionCache) { sConnectionCache.put(connectionId, connection); if (!initializeCache) { @@ -554,6 +558,10 @@ public final class AccessibilityInteractionClient } return cachedInfo; } + if (!cache.isEnabled()) { + // Skip prefetching if cache is disabled. + prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK; + } if (DEBUG) { Log.i(LOG_TAG, "Node cache miss for " + idToString(accessibilityWindowId, accessibilityNodeId)); @@ -970,9 +978,9 @@ public final class AccessibilityInteractionClient /** * Clears the cache associated with {@code connectionId} * @param connectionId the connection id - * TODO(207417185): Modify UnsupportedAppUsage */ - @UnsupportedAppUsage() + @UnsupportedAppUsage(maxTargetSdk = S, publicAlternatives = + "{@link android.accessibilityservice.AccessibilityService#clearCache()}") public void clearCache(int connectionId) { AccessibilityCache cache = getCache(connectionId); if (cache == null) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 9511958529e5..db7c6634c8a7 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -50,7 +50,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.LongArray; -import android.util.Pools.SynchronizedPool; import android.util.Size; import android.util.TypedValue; import android.view.SurfaceView; @@ -68,7 +67,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; /** * This class represents a node of the window content as well as actions that @@ -723,9 +721,6 @@ public class AccessibilityNodeInfo implements Parcelable { */ private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32; - // TODO(b/129300068): Remove sNumInstancesInUse. - private static AtomicInteger sNumInstancesInUse; - /** * Gets the accessibility view id which identifies a View in the view three. * @@ -769,11 +764,6 @@ public class AccessibilityNodeInfo implements Parcelable { return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId; } - // Housekeeping. - private static final int MAX_POOL_SIZE = 50; - private static final SynchronizedPool<AccessibilityNodeInfo> sPool = - new SynchronizedPool<>(MAX_POOL_SIZE); - private static final AccessibilityNodeInfo DEFAULT = new AccessibilityNodeInfo(); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -869,7 +859,7 @@ public class AccessibilityNodeInfo implements Parcelable { * @param info The other info. */ public AccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) { - init(info, false /* usePoolingInfo */); + init(info); } /** @@ -1009,13 +999,7 @@ public class AccessibilityNodeInfo implements Parcelable { if (refreshedInfo == null) { return false; } - // Hard-to-reproduce bugs seem to be due to some tools recycling a node on another - // thread. If that happens, the init will re-seal the node, which then is in a bad state - // when it is obtained. Enforce sealing again before we init to fail when a node has been - // recycled during a refresh to catch such errors earlier. - enforceSealed(); - init(refreshedInfo, true /* usePoolingInfo */); - refreshedInfo.recycle(); + init(refreshedInfo); return true; } @@ -3599,25 +3583,23 @@ public class AccessibilityNodeInfo implements Parcelable { * Returns a cached instance if such is available otherwise a new one * and sets the source. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link #AccessibilityNodeInfo(View)} instead. - * * @param source The source view. * @return An instance. * * @see #setSource(View) */ + @Deprecated public static AccessibilityNodeInfo obtain(View source) { - AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); - info.setSource(source); - return info; + return new AccessibilityNodeInfo(source); } /** * Returns a cached instance if such is available otherwise a new one * and sets the source. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link #AccessibilityNodeInfo(View, int)} instead. * * @param root The root of the virtual subtree. @@ -3626,71 +3608,45 @@ public class AccessibilityNodeInfo implements Parcelable { * * @see #setSource(View, int) */ + @Deprecated public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) { - AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); - info.setSource(root, virtualDescendantId); - return info; + return new AccessibilityNodeInfo(root, virtualDescendantId); } /** - * Returns a cached instance if such is available otherwise a new one. + * Instantiates a new AccessibilityNodeInfo. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link #AccessibilityNodeInfo()} instead. - * * @return An instance. */ + @Deprecated public static AccessibilityNodeInfo obtain() { - AccessibilityNodeInfo info = sPool.acquire(); - if (sNumInstancesInUse != null) { - sNumInstancesInUse.incrementAndGet(); - } - return (info != null) ? info : new AccessibilityNodeInfo(); + return new AccessibilityNodeInfo(); } /** - * Returns a cached instance if such is available or a new one is - * create. The returned instance is initialized from the given + * Instantiates a new AccessibilityNodeInfo initialized from the given * <code>info</code>. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link #AccessibilityNodeInfo(AccessibilityNodeInfo)} instead. - * * @param info The other info. * @return An instance. */ + @Deprecated public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) { - AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain(); - infoClone.init(info, true /* usePoolingInfo */); - return infoClone; + return new AccessibilityNodeInfo(info); } /** - * Return an instance back to be reused. - * <p> - * <strong>Note:</strong> You must not touch the object after calling this function. - * - * <p>In most situations object pooling is not beneficial, and recycling is not necessary. + * Would previously return an instance back to be reused. * - * @throws IllegalStateException If the info is already recycled. + * @deprecated Object pooling has been discontinued. Calling this function now will have + * no effect. */ - public void recycle() { - clear(); - sPool.release(this); - if (sNumInstancesInUse != null) { - sNumInstancesInUse.decrementAndGet(); - } - } - - /** - * Specify a counter that will be incremented on obtain() and decremented on recycle() - * - * @hide - */ - @TestApi - public static void setNumInstancesInUseCounter(AtomicInteger counter) { - sNumInstancesInUse = counter; - } + @Deprecated + public void recycle() {} /** * {@inheritDoc} @@ -3704,7 +3660,6 @@ public class AccessibilityNodeInfo implements Parcelable { writeToParcelNoRecycle(parcel, flags); // Since instances of this class are fetched via synchronous i.e. blocking // calls in IPCs we always recycle as soon as the instance is marshaled. - recycle(); } /** @hide */ @@ -4000,9 +3955,8 @@ public class AccessibilityNodeInfo implements Parcelable { * Initializes this instance from another one. * * @param other The other instance. - * @param usePoolingInfos whether using pooled object internally or not */ - private void init(AccessibilityNodeInfo other, boolean usePoolingInfos) { + private void init(AccessibilityNodeInfo other) { mSealed = other.mSealed; mSourceNodeId = other.mSourceNodeId; mParentNodeId = other.mParentNodeId; @@ -4062,11 +4016,7 @@ public class AccessibilityNodeInfo implements Parcelable { mExtras = other.mExtras != null ? new Bundle(other.mExtras) : null; - if (usePoolingInfos) { - initPoolingInfos(other); - } else { - initCopyInfos(other); - } + initCopyInfos(other); final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo; mTouchDelegateInfo = (otherInfo != null) @@ -4077,21 +4027,6 @@ public class AccessibilityNodeInfo implements Parcelable { mLeashedParentNodeId = other.mLeashedParentNodeId; } - private void initPoolingInfos(AccessibilityNodeInfo other) { - if (mRangeInfo != null) mRangeInfo.recycle(); - mRangeInfo = (other.mRangeInfo != null) - ? RangeInfo.obtain(other.mRangeInfo) : null; - if (mCollectionInfo != null) mCollectionInfo.recycle(); - mCollectionInfo = (other.mCollectionInfo != null) - ? CollectionInfo.obtain(other.mCollectionInfo) : null; - if (mCollectionItemInfo != null) mCollectionItemInfo.recycle(); - mCollectionItemInfo = (other.mCollectionItemInfo != null) - ? CollectionItemInfo.obtain(other.mCollectionItemInfo) : null; - if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle(); - mExtraRenderingInfo = (other.mExtraRenderingInfo != null) - ? ExtraRenderingInfo.obtain(other.mExtraRenderingInfo) : null; - } - private void initCopyInfos(AccessibilityNodeInfo other) { RangeInfo ri = other.mRangeInfo; mRangeInfo = (ri == null) ? null @@ -4205,27 +4140,24 @@ public class AccessibilityNodeInfo implements Parcelable { ? parcel.readBundle() : null; - if (mRangeInfo != null) mRangeInfo.recycle(); mRangeInfo = isBitSet(nonDefaultFields, fieldIndex++) - ? RangeInfo.obtain( + ? new RangeInfo( parcel.readInt(), parcel.readFloat(), parcel.readFloat(), parcel.readFloat()) : null; - if (mCollectionInfo != null) mCollectionInfo.recycle(); mCollectionInfo = isBitSet(nonDefaultFields, fieldIndex++) - ? CollectionInfo.obtain( + ? new CollectionInfo( parcel.readInt(), parcel.readInt(), parcel.readInt() == 1, parcel.readInt()) : null; - if (mCollectionItemInfo != null) mCollectionItemInfo.recycle(); mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++) - ? CollectionItemInfo.obtain( + ? new CollectionItemInfo( parcel.readString(), parcel.readInt(), parcel.readInt(), @@ -4241,8 +4173,7 @@ public class AccessibilityNodeInfo implements Parcelable { } if (isBitSet(nonDefaultFields, fieldIndex++)) { - if (mExtraRenderingInfo != null) mExtraRenderingInfo.recycle(); - mExtraRenderingInfo = ExtraRenderingInfo.obtain(); + mExtraRenderingInfo = new ExtraRenderingInfo(null); mExtraRenderingInfo.mLayoutSize = (Size) parcel.readValue(null); mExtraRenderingInfo.mTextSizeInPx = parcel.readFloat(); mExtraRenderingInfo.mTextSizeUnit = parcel.readInt(); @@ -4265,7 +4196,7 @@ public class AccessibilityNodeInfo implements Parcelable { * Clears the state of this instance. */ private void clear() { - init(DEFAULT, true /* usePoolingInfo */); + init(DEFAULT); } private static boolean isDefaultStandardAction(AccessibilityAction action) { @@ -5235,7 +5166,6 @@ public class AccessibilityNodeInfo implements Parcelable { * handled by the {@link AccessibilityNodeInfo} to which this object is attached. */ public static final class RangeInfo { - private static final int MAX_POOL_SIZE = 10; /** Range type: integer. */ public static final int RANGE_TYPE_INT = 0; @@ -5244,35 +5174,16 @@ public class AccessibilityNodeInfo implements Parcelable { /** Range type: percent with values from zero to one hundred. */ public static final int RANGE_TYPE_PERCENT = 2; - private static final SynchronizedPool<RangeInfo> sPool = - new SynchronizedPool<AccessibilityNodeInfo.RangeInfo>(MAX_POOL_SIZE); - private int mType; private float mMin; private float mMax; private float mCurrent; - - /** - * Obtains a pooled instance that is a clone of another one. - * - * <p>In most situations object pooling is not beneficial. Create a new instance using the - * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int, - * float, float, float)} instead. - * - * @param other The instance to clone. - * - * @hide - */ - public static RangeInfo obtain(RangeInfo other) { - return obtain(other.mType, other.mMin, other.mMax, other.mCurrent); - } - /** - * Obtains a pooled instance. + * Instantiates a new RangeInfo. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the - * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int, - * float, float, float)} instead. + * @deprecated Object pooling has been discontinued. Create a new instance using the + * constructor {@link AccessibilityNodeInfo.RangeInfo#RangeInfo(int, float, float, + * float)} instead. * * @param type The type of the range. * @param min The minimum value. Use {@code Float.NEGATIVE_INFINITY} if the range has no @@ -5281,17 +5192,9 @@ public class AccessibilityNodeInfo implements Parcelable { * maximum. * @param current The current value. */ + @Deprecated public static RangeInfo obtain(int type, float min, float max, float current) { - RangeInfo info = sPool.acquire(); - if (info == null) { - return new RangeInfo(type, min, max, current); - } - - info.mType = type; - info.mMin = min; - info.mMax = max; - info.mCurrent = current; - return info; + return new RangeInfo(type, min, max, current); } /** @@ -5354,12 +5257,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Recycles this instance. * - * <p>In most situations object pooling is not beneficial, and recycling is not necessary. + * @deprecated Object pooling has been discontinued. Calling this function now will have + * no effect. */ - void recycle() { - clear(); - sPool.release(this); - } + @Deprecated + void recycle() {} private void clear() { mType = 0; @@ -5392,20 +5294,15 @@ public class AccessibilityNodeInfo implements Parcelable { /** Selection mode where multiple items may be selected. */ public static final int SELECTION_MODE_MULTIPLE = 2; - private static final int MAX_POOL_SIZE = 20; - - private static final SynchronizedPool<CollectionInfo> sPool = - new SynchronizedPool<>(MAX_POOL_SIZE); - private int mRowCount; private int mColumnCount; private boolean mHierarchical; private int mSelectionMode; /** - * Obtains a pooled instance that is a clone of another one. + * Instantiates a CollectionInfo that is a clone of another one. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link * AccessibilityNodeInfo.CollectionInfo#CollectionInfo} instead. * @@ -5413,14 +5310,14 @@ public class AccessibilityNodeInfo implements Parcelable { * @hide */ public static CollectionInfo obtain(CollectionInfo other) { - return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical, + return new CollectionInfo(other.mRowCount, other.mColumnCount, other.mHierarchical, other.mSelectionMode); } /** * Obtains a pooled instance. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link * AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int, * boolean)} instead. @@ -5431,13 +5328,13 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static CollectionInfo obtain(int rowCount, int columnCount, boolean hierarchical) { - return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE); + return new CollectionInfo(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE); } /** * Obtains a pooled instance. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link * AccessibilityNodeInfo.CollectionInfo#CollectionInfo(int, int, * boolean, int)} instead. @@ -5454,16 +5351,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static CollectionInfo obtain(int rowCount, int columnCount, boolean hierarchical, int selectionMode) { - final CollectionInfo info = sPool.acquire(); - if (info == null) { - return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode); - } - - info.mRowCount = rowCount; - info.mColumnCount = columnCount; - info.mHierarchical = hierarchical; - info.mSelectionMode = selectionMode; - return info; + return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode); } /** @@ -5535,14 +5423,13 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Recycles this instance. + * Previously would recycle this instance. * - * <p>In most situations object pooling is not beneficial, and recycling is not necessary. + * @deprecated Object pooling has been discontinued. Calling this function now will have + * no effect. */ - void recycle() { - clear(); - sPool.release(this); - } + @Deprecated + void recycle() {} private void clear() { mRowCount = 0; @@ -5566,15 +5453,10 @@ public class AccessibilityNodeInfo implements Parcelable { * </p> */ public static final class CollectionItemInfo { - private static final int MAX_POOL_SIZE = 20; - - private static final SynchronizedPool<CollectionItemInfo> sPool = - new SynchronizedPool<>(MAX_POOL_SIZE); - /** - * Obtains a pooled instance that is a clone of another one. + * Instantiates a CollectionItemInfo that is a clone of another one. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo} * instead. @@ -5582,20 +5464,20 @@ public class AccessibilityNodeInfo implements Parcelable { * @param other The instance to clone. * @hide */ + @Deprecated public static CollectionItemInfo obtain(CollectionItemInfo other) { - return CollectionItemInfo.obtain(other.mRowTitle, other.mRowIndex, other.mRowSpan, - other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading, - other.mSelected); + return new CollectionItemInfo(other.mRowTitle, other.mRowIndex, other.mRowSpan, + other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading, + other.mSelected); } /** - * Obtains a pooled instance. + * Instantiates a new CollectionItemInfo. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int, * int, int, int, boolean)} instead. - * * @param rowIndex The row index at which the item is located. * @param rowSpan The number of rows the item spans. * @param columnIndex The column index at which the item is located. @@ -5603,37 +5485,39 @@ public class AccessibilityNodeInfo implements Parcelable { * @param heading Whether the item is a heading. (Prefer * {@link AccessibilityNodeInfo#setHeading(boolean)}). */ + @Deprecated public static CollectionItemInfo obtain(int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading) { - return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false); + return new CollectionItemInfo(rowIndex, rowSpan, columnIndex, columnSpan, heading, + false); } /** - * Obtains a pooled instance. + * Instantiates a new CollectionItemInfo. * - * <p>In most situations object pooling is not beneficial. Creates a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int, - * int, int, int, boolean, boolean)} instead. - * + * int, int, int, boolean)} instead. * @param rowIndex The row index at which the item is located. * @param rowSpan The number of rows the item spans. * @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)}) + * {@link AccessibilityNodeInfo#setHeading(boolean)}). * @param selected Whether the item is selected. */ + @Deprecated 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); + return new CollectionItemInfo(rowIndex, rowSpan, columnIndex, columnSpan, heading, + selected); } /** - * Obtains a pooled instance. + * Instantiates a new CollectionItemInfo. * - * <p>In most situations object pooling is not beneficial. Creates a new instance using the + * @deprecated Object pooling has been discontinued. Creates a new instance using the * constructor {@link * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int, * int, int, int, boolean, boolean)} instead. @@ -5648,25 +5532,13 @@ public class AccessibilityNodeInfo implements Parcelable { * {@link AccessibilityNodeInfo#setHeading(boolean)}) * @param selected Whether the item is selected. */ + @Deprecated @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(rowTitle, rowIndex, rowSpan, columnTitle, - columnIndex, columnSpan, heading, selected); - } - - info.mRowIndex = rowIndex; - info.mRowSpan = rowSpan; - info.mColumnIndex = columnIndex; - info.mColumnSpan = columnSpan; - info.mHeading = heading; - info.mSelected = selected; - info.mRowTitle = rowTitle; - info.mColumnTitle = columnTitle; - return info; + return new CollectionItemInfo(rowTitle, rowIndex, rowSpan, columnTitle, columnIndex, + columnSpan, heading, selected); } private boolean mHeading; @@ -5817,12 +5689,11 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Recycles this instance. * - * <p>In most situations object pooling is not beneficial, and recycling is not necessary. + * @deprecated Object pooling has been discontinued. Calling this function now will have + * no effect. */ - void recycle() { - clear(); - sPool.release(this); - } + @Deprecated + void recycle() {} private void clear() { mColumnIndex = 0; @@ -6151,34 +6022,34 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static final class ExtraRenderingInfo { private static final int UNDEFINED_VALUE = -1; - private static final int MAX_POOL_SIZE = 20; - private static final SynchronizedPool<ExtraRenderingInfo> sPool = - new SynchronizedPool<>(MAX_POOL_SIZE); private Size mLayoutSize; private float mTextSizeInPx = UNDEFINED_VALUE; private int mTextSizeUnit = UNDEFINED_VALUE; /** - * Obtains a pooled instance. + * Instantiates an ExtraRenderingInfo, by copying an existing one. + * * @hide + * @deprecated Object pooling has been discontinued. Create a new instance using the + * constructor {@link #ExtraRenderingInfo(ExtraRenderingInfo)} instead. */ + @Deprecated @NonNull public static ExtraRenderingInfo obtain() { - final ExtraRenderingInfo info = sPool.acquire(); - if (info == null) { - return new ExtraRenderingInfo(null); - } - return info; + return new ExtraRenderingInfo(null); } - /** Obtains a pooled instance that is a clone of another one. */ + /** + * Instantiates an ExtraRenderingInfo, by copying an existing one. + * + * @deprecated Object pooling has been discontinued. Create a new instance using the + * constructor {@link #ExtraRenderingInfo(ExtraRenderingInfo)} instead. + * @param other + */ + @Deprecated private static ExtraRenderingInfo obtain(ExtraRenderingInfo other) { - ExtraRenderingInfo extraRenderingInfo = ExtraRenderingInfo.obtain(); - extraRenderingInfo.mLayoutSize = other.mLayoutSize; - extraRenderingInfo.mTextSizeInPx = other.mTextSizeInPx; - extraRenderingInfo.mTextSizeUnit = other.mTextSizeUnit; - return extraRenderingInfo; + return new ExtraRenderingInfo(other); } /** @@ -6268,14 +6139,13 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Recycles this instance. + * Previously would recycle this instance. * - * <p>In most situations object pooling is not beneficial, and recycling is not necessary. + * @deprecated Object pooling has been discontinued. Calling this function now will have + * no effect. */ - void recycle() { - clear(); - sPool.release(this); - } + @Deprecated + void recycle() {} private void clear() { mLayoutSize = null; @@ -6291,7 +6161,7 @@ public class AccessibilityNodeInfo implements Parcelable { new Parcelable.Creator<AccessibilityNodeInfo>() { @Override public AccessibilityNodeInfo createFromParcel(Parcel parcel) { - AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + AccessibilityNodeInfo info = new AccessibilityNodeInfo(); info.initFromParcel(parcel); return info; } diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index f26abb2b46ef..426a3f448543 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -78,13 +78,6 @@ public class AccessibilityRecord { | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; - // Housekeeping - private static final int MAX_POOL_SIZE = 10; - private static final Object sPoolLock = new Object(); - private static AccessibilityRecord sPool; - private static int sPoolSize; - private AccessibilityRecord mNext; - private boolean mIsInPool; @UnsupportedAppUsage boolean mSealed; @@ -821,15 +814,14 @@ public class AccessibilityRecord { } /** - * Returns a cached instance if such is available or a new one is - * instantiated. The instance is initialized with data from the + * Instantiates a new record initialized with data from the * given record. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the - * constructor {@link #AccessibilityRecord(AccessibilityRecord)} instead. - * + * @deprecated Object pooling has been discontinued. Create a new instance using the + * constructor {@link #AccessibilityRecord()} instead. * @return An instance. */ + @Deprecated public static AccessibilityRecord obtain(AccessibilityRecord record) { AccessibilityRecord clone = AccessibilityRecord.obtain(); clone.init(record); @@ -837,51 +829,25 @@ public class AccessibilityRecord { } /** - * Returns a cached instance if such is available or a new one is - * instantiated. + * Instantiates a new record. * - * <p>In most situations object pooling is not beneficial. Create a new instance using the + * @deprecated Object pooling has been discontinued. Create a new instance using the * constructor {@link #AccessibilityRecord()} instead. - * * @return An instance. */ + @Deprecated public static AccessibilityRecord obtain() { - synchronized (sPoolLock) { - if (sPool != null) { - AccessibilityRecord record = sPool; - sPool = sPool.mNext; - sPoolSize--; - record.mNext = null; - record.mIsInPool = false; - return record; - } - return new AccessibilityRecord(); - } + return new AccessibilityRecord(); } /** - * Return an instance back to be reused. - * <p> - * <strong>Note:</strong> You must not touch the object after calling this function. + * Would previously return an instance back to be reused. * - * <p>In most situations object pooling is not beneficial, and recycling is not necessary. - * - * @throws IllegalStateException If the record is already recycled. + * @deprecated Object pooling has been discontinued. Calling this function now will have + * no effect. */ - public void recycle() { - if (mIsInPool) { - throw new IllegalStateException("Record already recycled!"); - } - clear(); - synchronized (sPoolLock) { - if (sPoolSize <= MAX_POOL_SIZE) { - mNext = sPool; - sPool = this; - mIsInPool = true; - sPoolSize++; - } - } - } + @Deprecated + public void recycle() { } /** * Initialize this record from another one. diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 11220561b00c..bb13c1e78964 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -472,6 +472,24 @@ public final class AutofillManager { public static final String DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT = "augmented_service_request_timeout"; + /** + * Sets allowed list for the autofill compatibility mode. + * + * The list of packages is {@code ":"} colon delimited, and each entry has the name of the + * package and an optional list of url bar resource ids (the list is delimited by + * brackets&mdash{@code [} and {@code ]}&mdash and is also comma delimited). + * + * <p>For example, a list with 3 packages {@code p1}, {@code p2}, and {@code p3}, where + * package {@code p1} have one id ({@code url_bar}, {@code p2} has none, and {@code p3 } + * have 2 ids {@code url_foo} and {@code url_bas}) would be + * {@code p1[url_bar]:p2:p3[url_foo,url_bas]} + * + * @hide + */ + @TestApi + public static final String DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = + "compat_mode_allowed_packages"; + /** @hide */ public static final int RESULT_OK = 0; /** @hide */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 7566ceab334d..3583cd4c6d8b 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -86,6 +86,7 @@ import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillManager; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.DirectBootAwareness; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; @@ -1234,6 +1235,26 @@ public final class InputMethodManager { } /** + * Returns the list of installed input methods for the specified user. + * + * @param userId user ID to query + * @param directBootAwareness {@code true} if caller want to query installed input methods list + * on user locked state. + * @return {@link List} of {@link InputMethodInfo}. + * @hide + */ + @RequiresPermission(INTERACT_ACROSS_USERS_FULL) + @NonNull + public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId, + @DirectBootAwareness int directBootAwareness) { + try { + return mService.getAwareLockedInputMethodList(userId, directBootAwareness); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the list of enabled input methods. * * <p>On multi user environment, this API returns a result for the calling process user.</p> @@ -2072,6 +2093,10 @@ public final class InputMethodManager { + ", ic=" + ic + ", tba=" + tba + ", handler=" + icHandler); } view.onInputConnectionOpenedInternal(ic, tba, icHandler); + final ViewRootImpl viewRoot = view.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.getHandwritingInitiator().onInputConnectionCreated(view, tba); + } } return true; diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java index 60688eaa3600..ba45b85e08a3 100644 --- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java +++ b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; +import android.provider.DeviceConfig; import java.util.Objects; @@ -40,6 +41,11 @@ public final class SelectionToolbarManager { */ public static final String LOG_TAG = "SelectionToolbar"; + /** + * Whether system selection toolbar is enabled. + */ + private static final String REMOTE_SELECTION_TOOLBAR_ENABLED = + "remote_selection_toolbar_enabled"; @NonNull private final Context mContext; @@ -86,4 +92,21 @@ public final class SelectionToolbarManager { throw e.rethrowFromSystemServer(); } } + + private boolean isRemoteSelectionToolbarEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR, + REMOTE_SELECTION_TOOLBAR_ENABLED, false); + } + + /** + * Returns {@code true} if remote render selection toolbar enabled, otherwise + * returns {@code false}. + */ + public static boolean isRemoteSelectionToolbarEnabled(Context context) { + SelectionToolbarManager manager = context.getSystemService(SelectionToolbarManager.class); + if (manager != null) { + return manager.isRemoteSelectionToolbarEnabled(); + } + return false; + } } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a5f3febafa9a..6d58ee22b159 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -301,6 +301,13 @@ public class RemoteViews implements Parcelable, Filter { public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4; /** + * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is + * intentionally a different instance in order to trick Bundle reader so that it doesn't allow + * lazy initialization. + */ + private static final Parcel.ReadWriteHelper ALTERNATIVE_DEFAULT = new Parcel.ReadWriteHelper(); + + /** * Used to restrict the views which can be inflated * * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) @@ -1856,7 +1863,18 @@ public class RemoteViews implements Parcelable, Filter { this.value = in.readTypedObject(Bitmap.CREATOR); break; case BUNDLE: - this.value = in.readBundle(); + // Because we use Parcel.allowSquashing() when writing, and that affects + // how the contents of Bundles are written, we need to ensure the bundle is + // unparceled immediately, not lazily. Setting a custom ReadWriteHelper + // just happens to have that effect on Bundle.readFromParcel(). + // TODO(b/212731590): build this state tracking into Bundle + if (in.hasReadWriteHelper()) { + this.value = in.readBundle(); + } else { + in.setReadWriteHelper(ALTERNATIVE_DEFAULT); + this.value = in.readBundle(); + in.setReadWriteHelper(null); + } break; case INTENT: this.value = in.readTypedObject(Intent.CREATOR); diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 862829bf9b55..dbf3570b1d24 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -74,6 +74,9 @@ import java.util.List; * <p> * Note that toasts being sent from the background are rate limited, so avoid sending such toasts * in quick succession. + * <p> + * Starting with Android 12 (API level 31), apps targeting Android 12 or newer will have + * their toasts limited to two lines. * * <div class="special reference"> * <h3>Developer Guides</h3> diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index 090dbff488e9..3f65f475fbd5 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -22,6 +22,7 @@ import android.annotation.StyleRes; import android.annotation.SuppressLint; import android.annotation.UiThread; import android.app.Activity; +import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.AppGlobals; import android.content.Context; @@ -48,13 +49,13 @@ public interface SplashScreen { */ int SPLASH_SCREEN_STYLE_UNDEFINED = -1; /** - * Force splash screen to be empty. - * @hide + * Flag to be used with {@link ActivityOptions#setSplashScreenStyle}, to avoid showing the + * splash screen icon of the launched activity */ int SPLASH_SCREEN_STYLE_EMPTY = 0; /** - * Force splash screen to show icon. - * @hide + * Flag to be used with {@link ActivityOptions#setSplashScreenStyle}, to show the splash screen + * icon of the launched activity. */ int SPLASH_SCREEN_STYLE_ICON = 1; diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index bc3c2f5872fc..025f7118334c 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -190,7 +190,7 @@ public class ChooserActivity extends ResolverActivity implements * 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 = + public static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE = "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE"; /** diff --git a/core/java/com/android/internal/inputmethod/DirectBootAwareness.java b/core/java/com/android/internal/inputmethod/DirectBootAwareness.java new file mode 100644 index 000000000000..51f914f737e9 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/DirectBootAwareness.java @@ -0,0 +1,47 @@ +/* + * 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.internal.inputmethod; + + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; + +/** + * Specifies the decided filtering mode regarding IMEs' DirectBoot awareness when querying IMEs. + */ +@Retention(SOURCE) +@IntDef({DirectBootAwareness.AUTO, DirectBootAwareness.ANY}) +public @interface DirectBootAwareness { + /** + * The same semantics as {@link android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO}, that + * is, if the user to be queried is still locked, then only DirectBoot-aware IMEs will be + * matched. If the user to be queried is already unlocked, then IMEs will not be filtered out + * based on their DirectBoot awareness. + */ + int AUTO = 0; + /** + * The same semantics as specifying <strong>both</strong> + * {@link android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE} and + * {@link android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE}, that is, IME will never + * be filtered out based on their DirectBoot awareness, no matter whether the user to be queried + * is still locked or already unlocked. + */ + int ANY = 1; +} diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index 662ed6b9306b..0470444a16e8 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -36,6 +36,7 @@ import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; import android.view.View; +import android.view.ViewRootImpl; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.DumpableInputConnection; @@ -350,8 +351,19 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { } if (handler.getLooper().isCurrentThread()) { servedView.onInputConnectionClosedInternal(); + final ViewRootImpl viewRoot = servedView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.getHandwritingInitiator().onInputConnectionClosed(servedView); + } } else { handler.post(servedView::onInputConnectionClosedInternal); + handler.post(() -> { + final ViewRootImpl viewRoot = servedView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.getHandwritingInitiator() + .onInputConnectionClosed(servedView); + } + }); } } } diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 879e0a8cfe10..b0fce8f18742 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -160,6 +160,11 @@ public class BatteryStatsHistory { mHistoryDir = null; mHistoryBuffer = historyBuffer; } + + public File getHistoryDirectory() { + return mHistoryDir; + } + /** * Set the active file that mHistoryBuffer is backed up into. * @@ -375,12 +380,26 @@ public class BatteryStatsHistory { } /** - * Read all history files and serialize into a big Parcel. This is to send history files to - * Settings app since Settings app can not access /data/system directory. - * Checkin file also call this method. + * Read all history files and serialize into a big Parcel. + * Checkin file calls this method. + * * @param out the output parcel */ public void writeToParcel(Parcel out) { + writeToParcel(out, false /* useBlobs */); + } + + /** + * This is for Settings app, when Settings app receives big history parcel, it call + * this method to parse it into list of parcels. + * @param out the output parcel + */ + public void writeToBatteryUsageStatsParcel(Parcel out) { + out.writeBlob(mHistoryBuffer.marshall()); + writeToParcel(out, true /* useBlobs */); + } + + private void writeToParcel(Parcel out, boolean useBlobs) { final long start = SystemClock.uptimeMillis(); out.writeInt(mFileNumbers.size() - 1); for(int i = 0; i < mFileNumbers.size() - 1; i++) { @@ -391,7 +410,12 @@ public class BatteryStatsHistory { } catch(Exception e) { Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e); } - out.writeByteArray(raw); + if (useBlobs) { + out.writeBlob(raw); + } else { + // Avoiding blobs in the check-in file for compatibility + out.writeByteArray(raw); + } } if (DEBUG) { Slog.d(TAG, "writeToParcel duration ms:" + (SystemClock.uptimeMillis() - start)); @@ -399,18 +423,36 @@ public class BatteryStatsHistory { } /** - * This is for Settings app, when Settings app receives big history parcel, it call - * this method to parse it into list of parcels. - * Checkin file also call this method. + * Reads a BatteryStatsHistory from a parcel written with + * the {@link #writeToBatteryUsageStatsParcel} method. + */ + public static BatteryStatsHistory createFromBatteryUsageStatsParcel(Parcel in) { + final byte[] historyBlob = in.readBlob(); + + Parcel historyBuffer = Parcel.obtain(); + historyBuffer.unmarshall(historyBlob, 0, historyBlob.length); + + BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer); + history.readFromParcel(in, true /* useBlobs */); + return history; + } + + /** + * This is for the check-in file, which has all history files embedded. + * * @param in the input parcel. */ public void readFromParcel(Parcel in) { + readFromParcel(in, false /* useBlobs */); + } + + private void readFromParcel(Parcel in, boolean useBlobs) { final long start = SystemClock.uptimeMillis(); mHistoryParcels = new ArrayList<>(); final int count = in.readInt(); for(int i = 0; i < count; i++) { - byte[] temp = in.createByteArray(); - if (temp.length == 0) { + byte[] temp = useBlobs ? in.readBlob() : in.createByteArray(); + if (temp == null || temp.length == 0) { continue; } Parcel p = Parcel.obtain(); diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index 88425be84313..69b7b4e8df0f 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -21,13 +21,16 @@ import android.hardware.SensorManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.Parcel; import android.os.SystemClock; import android.os.UidBatteryConsumer; import android.util.Log; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -133,9 +136,12 @@ public class BatteryUsageStatsProvider { */ @VisibleForTesting public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) { - return getBatteryUsageStats(query, currentTimeMillis()); + synchronized (mStats) { + return getBatteryUsageStats(query, currentTimeMillis()); + } } + @GuardedBy("mStats") private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query, long currentTimeMs) { if (query.getToTimestamp() == 0) { @@ -145,6 +151,7 @@ public class BatteryUsageStatsProvider { } } + @GuardedBy("mStats") private BatteryUsageStats getCurrentBatteryUsageStats(BatteryUsageStatsQuery query, long currentTimeMs) { final long realtimeUs = elapsedRealtime() * 1000; @@ -189,7 +196,18 @@ public class BatteryUsageStatsProvider { } BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats; - batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.mHistoryBuffer); + + // Make a copy of battery history to avoid concurrent modification. + Parcel historyBuffer = Parcel.obtain(); + historyBuffer.appendFrom(batteryStatsImpl.mHistoryBuffer, 0, + batteryStatsImpl.mHistoryBuffer.dataSize()); + + final File systemDir = + batteryStatsImpl.mBatteryStatsHistory.getHistoryDirectory().getParentFile(); + final BatteryStatsHistory batteryStatsHistory = + new BatteryStatsHistory(batteryStatsImpl, systemDir, historyBuffer); + + batteryUsageStatsBuilder.setBatteryHistory(batteryStatsHistory); } return batteryUsageStatsBuilder.build(); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 6541b14b9070..ba7a0ef893d0 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -121,7 +121,7 @@ import com.android.internal.view.menu.MenuHelper; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.DecorCaptionView; -import com.android.internal.widget.FloatingToolbar; +import com.android.internal.widget.floatingtoolbar.FloatingToolbar; import java.util.List; import java.util.function.Consumer; diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index 76aa7a015d2f..36b7ee5b3b62 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -15,6 +15,7 @@ */ package com.android.internal.policy; +import android.content.Intent; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardStateCallback; @@ -113,7 +114,20 @@ oneway interface IKeyguardService { /** * Notifies the Keyguard that the power key was pressed while locked and launched Home rather - * than putting the device to sleep or waking up. + * than putting the device to sleep or waking up. Note that it's called only if the device is + * interactive. */ void onShortPowerPressedGoHome(); + + /** + * Notifies the Keyguard that it needs to bring up a bouncer and then launch the intent as soon + * as user unlocks the watch. + */ + void dismissKeyguardToLaunch(in Intent intentToLaunch); + + /** + * Notifies the Keyguard that a key was pressed while locked so the Keyguard can handle it. + * Note that it's called only if the device is interactive. + */ + void onSystemKeyPressed(int keycode); } diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java index 36913b729f1b..06e69f2d9859 100644 --- a/core/java/com/android/internal/view/FloatingActionMode.java +++ b/core/java/com/android/internal/view/FloatingActionMode.java @@ -33,7 +33,7 @@ import android.widget.PopupWindow; import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; -import com.android.internal.widget.FloatingToolbar; +import com.android.internal.widget.floatingtoolbar.FloatingToolbar; import java.util.Arrays; import java.util.Objects; diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 350ec33326bd..2dc7c42c95a9 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -35,6 +35,7 @@ interface IInputMethodManager { // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(int userId); + List<InputMethodInfo> getAwareLockedInputMethodList(int userId, int directBootAwareness); // TODO: Use ParceledListSlice instead List<InputMethodInfo> getEnabledInputMethodList(int userId); List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId, diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbar.java index a0bf9b59cc14..e75f3729bfac 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbar.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.widget; +package com.android.internal.widget.floatingtoolbar; import android.annotation.Nullable; import android.graphics.Rect; @@ -50,14 +50,10 @@ public final class FloatingToolbar { private final FloatingToolbarPopup mPopup; private final Rect mContentRect = new Rect(); - private final Rect mPreviousContentRect = new Rect(); private Menu mMenu; private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER; - private int mSuggestedWidth; - private boolean mWidthChanged = true; - private final OnLayoutChangeListener mOrientationChangeHandler = new OnLayoutChangeListener() { private final Rect mNewRect = new Rect(); @@ -71,7 +67,7 @@ public final class FloatingToolbar { mNewRect.set(newLeft, newRight, newTop, newBottom); mOldRect.set(oldLeft, oldRight, oldTop, oldBottom); if (mPopup.isShowing() && !mNewRect.equals(mOldRect)) { - mWidthChanged = true; + mPopup.setWidthChanged(true); updateLayout(); } } @@ -114,7 +110,7 @@ public final class FloatingToolbar { // TODO(b/65172902): Pass context in constructor when DecorView (and other callers) // supports multi-display. mWindow = Objects.requireNonNull(window); - mPopup = new FloatingToolbarPopup(window.getContext(), window.getDecorView()); + mPopup = FloatingToolbarPopup.createInstance(window.getContext(), window.getDecorView()); } /** @@ -159,11 +155,7 @@ public final class FloatingToolbar { * toolbar. */ public FloatingToolbar setSuggestedWidth(int suggestedWidth) { - // Check if there's been a substantial width spec change. - int difference = Math.abs(suggestedWidth - mSuggestedWidth); - mWidthChanged = difference > (mSuggestedWidth * 0.2); - - mSuggestedWidth = suggestedWidth; + mPopup.setSuggestedWidth(suggestedWidth); return this; } @@ -232,19 +224,7 @@ public final class FloatingToolbar { private void doShow() { List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu); menuItems.sort(mMenuItemComparator); - if (mPopup.isLayoutRequired(menuItems) || mWidthChanged) { - mPopup.dismiss(); - mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth); - } else { - mPopup.updateMenuItems(menuItems, mMenuItemClickListener); - } - if (!mPopup.isShowing()) { - mPopup.show(mContentRect); - } else if (!mPreviousContentRect.equals(mContentRect)) { - mPopup.updateCoordinates(mContentRect); - } - mWidthChanged = false; - mPreviousContentRect.set(mContentRect); + mPopup.show(menuItems, mMenuItemClickListener, mContentRect); } /** diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java new file mode 100644 index 000000000000..f47700c6e739 --- /dev/null +++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java @@ -0,0 +1,100 @@ +/* + * 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.internal.widget.floatingtoolbar; + +import android.content.Context; +import android.graphics.Rect; +import android.view.MenuItem; +import android.view.View; +import android.view.selectiontoolbar.SelectionToolbarManager; +import android.widget.PopupWindow; + +import java.util.List; + +/** + * A popup window used by the {@link FloatingToolbar} to render menu items. + * + */ +public interface FloatingToolbarPopup { + + /** + * Sets the suggested dp width of this floating toolbar. + * The actual width will be about this size but there are no guarantees that it will be exactly + * the suggested width. + */ + void setSuggestedWidth(int suggestedWidth); + + /** + * Sets if the floating toolbar width changed. + */ + void setWidthChanged(boolean widthChanged); + + /** + * Shows this popup at the specified coordinates. + * The specified coordinates may be adjusted to make sure the popup is entirely on-screen. + */ + void show(List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener, + Rect contentRect); + + /** + * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op. + */ + void dismiss(); + + /** + * Hides this popup. This is a no-op if this popup is not showing. + * Use {@link #isHidden()} to distinguish between a hidden and a dismissed popup. + */ + void hide(); + + /** + * Returns {@code true} if this popup is currently showing. {@code false} otherwise. + */ + boolean isShowing(); + + /** + * Returns {@code true} if this popup is currently hidden. {@code false} otherwise. + */ + boolean isHidden(); + + /** + * Makes this toolbar "outside touchable" and sets the onDismissListener. + * + * @param outsideTouchable if true, the popup will be made "outside touchable" and + * "non focusable". The reverse will happen if false. + * @param onDismiss + * + * @return true if the "outsideTouchable" setting was modified. Otherwise returns false + * + * @see PopupWindow#setOutsideTouchable(boolean) + * @see PopupWindow#setFocusable(boolean) + * @see PopupWindow.OnDismissListener + */ + boolean setOutsideTouchable(boolean outsideTouchable, PopupWindow.OnDismissListener onDismiss); + + /** + * Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar + * enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation. + */ + static FloatingToolbarPopup createInstance(Context context, View parent) { + boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context); + return enabled + ? new RemoteFloatingToolbarPopup(context, parent) + : new LocalFloatingToolbarPopup(context, parent); + } + +} diff --git a/core/java/com/android/internal/widget/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index e0388f6f3cc0..80d8bd78e746 100644 --- a/core/java/com/android/internal/widget/FloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.widget; +package com.android.internal.widget.floatingtoolbar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -70,13 +70,13 @@ import java.util.Map; import java.util.Objects; /** - * A popup window used by the floating toolbar. + * A popup window used by the floating toolbar to render menu items in the local app process. * * This class is responsible for the rendering/animation of the floating toolbar. * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button * to transition between panels. */ -public final class FloatingToolbarPopup { +public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { /* Minimum and maximum number of items allowed in the overflow. */ private static final int MIN_OVERFLOW_SIZE = 2; @@ -94,7 +94,7 @@ public final class FloatingToolbarPopup { private final ViewGroup mContentContainer; // holds all contents. private final ViewGroup mMainPanel; // holds menu items that are initially displayed. // holds menu items hidden in the overflow. - private final FloatingToolbarPopup.OverflowPanel mOverflowPanel; + private final OverflowPanel mOverflowPanel; private final ImageButton mOverflowButton; // opens/closes the overflow. /* overflow button drawables. */ private final Drawable mArrow; @@ -102,8 +102,7 @@ public final class FloatingToolbarPopup { private final AnimatedVectorDrawable mToArrow; private final AnimatedVectorDrawable mToOverflow; - private final FloatingToolbarPopup.OverflowPanelViewHelper - mOverflowPanelViewHelper; + private final OverflowPanelViewHelper mOverflowPanelViewHelper; /* Animation interpolators. */ private final Interpolator mLogAccelerateInterpolator; @@ -138,7 +137,7 @@ public final class FloatingToolbarPopup { private final int mIconTextSpacing; /** - * @see FloatingToolbarPopup.OverflowPanelViewHelper#preparePopupContent(). + * @see OverflowPanelViewHelper#preparePopupContent(). */ private final Runnable mPreparePopupContentRTLHelper = new Runnable() { @Override @@ -184,16 +183,20 @@ public final class FloatingToolbarPopup { private int mTransitionDurationScale; // Used to scale the toolbar transition duration. + private final Rect mPreviousContentRect = new Rect(); + private int mSuggestedWidth; + private boolean mWidthChanged = true; + /** * Initializes a new floating toolbar popup. * * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token * from. */ - public FloatingToolbarPopup(Context context, View parent) { + public LocalFloatingToolbarPopup(Context context, View parent) { mParent = Objects.requireNonNull(parent); mContext = applyDefaultTheme(context); - mContentContainer = createContentContainer(context); + mContentContainer = createContentContainer(mContext); mPopupWindow = createPopupWindow(mContentContainer); mMarginHorizontal = parent.getResources() .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin); @@ -205,7 +208,7 @@ public final class FloatingToolbarPopup { .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing); // Interpolators - mLogAccelerateInterpolator = new FloatingToolbarPopup.LogAccelerateInterpolator(); + mLogAccelerateInterpolator = new LogAccelerateInterpolator(); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( mContext, android.R.interpolator.fast_out_slow_in); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( @@ -231,8 +234,7 @@ public final class FloatingToolbarPopup { mOverflowButton = createOverflowButton(); mOverflowButtonSize = measure(mOverflowButton); mMainPanel = createMainPanel(); - mOverflowPanelViewHelper = - new FloatingToolbarPopup.OverflowPanelViewHelper(mContext, mIconTextSpacing); + mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing); mOverflowPanel = createOverflowPanel(); // Animation. Need views. @@ -263,19 +265,7 @@ public final class FloatingToolbarPopup { }); } - /** - * Makes this toolbar "outside touchable" and sets the onDismissListener. - * - * @param outsideTouchable if true, the popup will be made "outside touchable" and - * "non focusable". The reverse will happen if false. - * @param onDismiss - * - * @return true if the "outsideTouchable" setting was modified. Otherwise returns false - * - * @see PopupWindow#setOutsideTouchable(boolean) - * @see PopupWindow#setFocusable(boolean) - * @see PopupWindow.OnDismissListener - */ + @Override public boolean setOutsideTouchable( boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { boolean ret = false; @@ -293,7 +283,7 @@ public final class FloatingToolbarPopup { * Lays out buttons for the specified menu items. * Requires a subsequent call to {@link FloatingToolbar#show()} to show the items. */ - public void layoutMenuItems( + private void layoutMenuItems( List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener, int suggestedWidth) { @@ -314,7 +304,7 @@ public final class FloatingToolbarPopup { * * @see #isLayoutRequired(List<MenuItem>) */ - public void updateMenuItems( + private void updateMenuItems( List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) { mMenuItems.clear(); for (MenuItem menuItem : menuItems) { @@ -326,15 +316,42 @@ public final class FloatingToolbarPopup { /** * Returns true if this popup needs a relayout to properly render the specified menu items. */ - public boolean isLayoutRequired(List<MenuItem> menuItems) { + private boolean isLayoutRequired(List<MenuItem> menuItems) { return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values()); } - /** - * Shows this popup at the specified coordinates. - * The specified coordinates may be adjusted to make sure the popup is entirely on-screen. - */ - public void show(Rect contentRectOnScreen) { + @Override + public void setWidthChanged(boolean widthChanged) { + mWidthChanged = widthChanged; + } + + @Override + public void setSuggestedWidth(int suggestedWidth) { + // Check if there's been a substantial width spec change. + int difference = Math.abs(suggestedWidth - mSuggestedWidth); + mWidthChanged = difference > (mSuggestedWidth * 0.2); + mSuggestedWidth = suggestedWidth; + } + + @Override + public void show(List<MenuItem> menuItems, + MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) { + if (isLayoutRequired(menuItems) || mWidthChanged) { + dismiss(); + layoutMenuItems(menuItems, menuItemClickListener, mSuggestedWidth); + } else { + updateMenuItems(menuItems, menuItemClickListener); + } + if (!isShowing()) { + show(contentRect); + } else if (!mPreviousContentRect.equals(contentRect)) { + updateCoordinates(contentRect); + } + mWidthChanged = false; + mPreviousContentRect.set(contentRect); + } + + private void show(Rect contentRectOnScreen) { Objects.requireNonNull(contentRectOnScreen); if (isShowing()) { @@ -357,9 +374,7 @@ public final class FloatingToolbarPopup { runShowAnimation(); } - /** - * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op. - */ + @Override public void dismiss() { if (mDismissed) { return; @@ -373,10 +388,7 @@ public final class FloatingToolbarPopup { setZeroTouchableSurface(); } - /** - * Hides this popup. This is a no-op if this popup is not showing. - * Use {@link #isHidden()} to distinguish between a hidden and a dismissed popup. - */ + @Override public void hide() { if (!isShowing()) { return; @@ -387,16 +399,12 @@ public final class FloatingToolbarPopup { setZeroTouchableSurface(); } - /** - * Returns {@code true} if this popup is currently showing. {@code false} otherwise. - */ + @Override public boolean isShowing() { return !mDismissed && !mHidden; } - /** - * Returns {@code true} if this popup is currently hidden. {@code false} otherwise. - */ + @Override public boolean isHidden() { return mHidden; } @@ -406,7 +414,7 @@ public final class FloatingToolbarPopup { * The specified coordinates may be adjusted to make sure the popup is entirely on-screen. * This is a no-op if this popup is not showing. */ - public void updateCoordinates(Rect contentRectOnScreen) { + private void updateCoordinates(Rect contentRectOnScreen) { Objects.requireNonNull(contentRectOnScreen); if (!isShowing() || !mPopupWindow.isShowing()) { @@ -1206,9 +1214,8 @@ public final class FloatingToolbarPopup { return overflowButton; } - private FloatingToolbarPopup.OverflowPanel createOverflowPanel() { - final FloatingToolbarPopup.OverflowPanel - overflowPanel = new FloatingToolbarPopup.OverflowPanel(this); + private OverflowPanel createOverflowPanel() { + final OverflowPanel overflowPanel = new OverflowPanel(this); overflowPanel.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); overflowPanel.setDivider(null); @@ -1307,9 +1314,9 @@ public final class FloatingToolbarPopup { */ private static final class OverflowPanel extends ListView { - private final FloatingToolbarPopup mPopup; + private final LocalFloatingToolbarPopup mPopup; - OverflowPanel(FloatingToolbarPopup popup) { + OverflowPanel(LocalFloatingToolbarPopup popup) { super(Objects.requireNonNull(popup).mContext); this.mPopup = popup; setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3); diff --git a/core/java/com/android/internal/widget/floatingtoolbar/OWNERS b/core/java/com/android/internal/widget/floatingtoolbar/OWNERS new file mode 100644 index 000000000000..ed9425cc26c9 --- /dev/null +++ b/core/java/com/android/internal/widget/floatingtoolbar/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/selectiontoolbar/OWNERS diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java new file mode 100644 index 000000000000..b3a8128012fc --- /dev/null +++ b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java @@ -0,0 +1,88 @@ +/* + * 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.internal.widget.floatingtoolbar; + +import android.content.Context; +import android.graphics.Rect; +import android.view.MenuItem; +import android.view.View; +import android.view.selectiontoolbar.SelectionToolbarManager; +import android.widget.PopupWindow; + +import java.util.List; +import java.util.Objects; + +/** + * A popup window used by the floating toolbar to render menu items in the remote system process. + * + * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button + * to transition between panels. + */ +public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup { + + private final SelectionToolbarManager mSelectionToolbarManager; + // Parent for the popup window. + private final View mParent; + + public RemoteFloatingToolbarPopup(Context context, View parent) { + // TODO: implement it + mParent = Objects.requireNonNull(parent); + mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class); + } + + @Override + public void show(List<MenuItem> menuItems, + MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) { + // TODO: implement it + } + + @Override + public void hide() { + // TODO: implement it + } + + @Override + public void setSuggestedWidth(int suggestedWidth) { + // TODO: implement it + } + + @Override + public void setWidthChanged(boolean widthChanged) { + // no-op + } + + @Override + public void dismiss() { + // TODO: implement it + } + + @Override + public boolean isHidden() { + return false; + } + + @Override + public boolean isShowing() { + return false; + } + + @Override + public boolean setOutsideTouchable(boolean outsideTouchable, + PopupWindow.OnDismissListener onDismiss) { + return false; + } +} diff --git a/core/java/com/android/server/NetworkManagementSocketTagger.java b/core/java/com/android/server/NetworkManagementSocketTagger.java index 26ff192521da..d89566c9119c 100644 --- a/core/java/com/android/server/NetworkManagementSocketTagger.java +++ b/core/java/com/android/server/NetworkManagementSocketTagger.java @@ -70,8 +70,8 @@ public final class NetworkManagementSocketTagger extends SocketTagger { Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x" + Integer.toHexString(options.statsTag) + ", statsUid=" + options.statsUid); } - if (options.statsTag == -1 && StrictMode.vmUntaggedSocketEnabled()) { - StrictMode.onUntaggedSocket(); + if (options.statsTag == -1) { + StrictMode.noteUntaggedSocket(); } // TODO: skip tagging when options would be no-op tagSocketFd(fd, options.statsTag, options.statsUid); diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index d66f4614e0e8..39f17e510a1c 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -849,8 +849,8 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); } } break; - case "updatable-library": - // "updatable-library" is meant to behave exactly like "library" + case "apex-library": + // "apex-library" is meant to behave exactly like "library" case "library": { if (allowLibs) { String lname = parser.getAttributeValue(null, "name"); diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 78e5adc2ccd1..55f136997aad 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -37,16 +37,6 @@ static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) { return reinterpret_cast<jlong>(queue.get()); } -static jlong nativeCreateAndUpdate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl, - jlong width, jlong height, jint format) { - ScopedUtfChars name(env, jName); - sp<BLASTBufferQueue> queue = - new BLASTBufferQueue(name.c_str(), reinterpret_cast<SurfaceControl*>(surfaceControl), - width, height, format); - queue->incStrong((void*)nativeCreate); - return reinterpret_cast<jlong>(queue.get()); -} - static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); queue->decStrong((void*)nativeCreate); @@ -91,11 +81,15 @@ static void nativeApplyPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr, queue->applyPendingTransactions(frameNum); } +static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceControl) { + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); + return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl)); +} + static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off {"nativeCreate", "(Ljava/lang/String;)J", (void*)nativeCreate}, - {"nativeCreateAndUpdate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreateAndUpdate}, {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface}, {"nativeDestroy", "(J)V", (void*)nativeDestroy}, {"nativeSetSyncTransaction", "(JJZ)V", (void*)nativeSetSyncTransaction}, @@ -103,6 +97,7 @@ static const JNINativeMethod gMethods[] = { {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction}, {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions}, + {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl}, // clang-format on }; diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 0b0870d9700a..3e2b25853a6a 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2782,13 +2782,13 @@ static jboolean android_media_AudioSystem_canBeSpatialized(JNIEnv *env, jobject static jint convertAudioDirectModeFromNative(audio_direct_mode_t directMode) { jint result = DIRECT_NOT_SUPPORTED; - if ((directMode | AUDIO_DIRECT_OFFLOAD_SUPPORTED) != AUDIO_DIRECT_NOT_SUPPORTED) { + if ((directMode & AUDIO_DIRECT_OFFLOAD_SUPPORTED) != AUDIO_DIRECT_NOT_SUPPORTED) { result |= DIRECT_OFFLOAD_SUPPORTED; } - if ((directMode | AUDIO_DIRECT_OFFLOAD_GAPLESS_SUPPORTED) != AUDIO_DIRECT_NOT_SUPPORTED) { + if ((directMode & AUDIO_DIRECT_OFFLOAD_GAPLESS_SUPPORTED) != AUDIO_DIRECT_NOT_SUPPORTED) { result |= DIRECT_OFFLOAD_GAPLESS_SUPPORTED; } - if ((directMode | AUDIO_DIRECT_BITSTREAM_SUPPORTED) != AUDIO_DIRECT_NOT_SUPPORTED) { + if ((directMode & AUDIO_DIRECT_BITSTREAM_SUPPORTED) != AUDIO_DIRECT_NOT_SUPPORTED) { result |= DIRECT_BITSTREAM_SUPPORTED; } return result; @@ -2799,7 +2799,7 @@ static jint android_media_AudioSystem_getDirectPlaybackSupport(JNIEnv *env, jobj JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique(); jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get()); if (jStatus != (jint)AUDIO_JAVA_SUCCESS) { - return AUDIO_DIRECT_NOT_SUPPORTED; + return DIRECT_NOT_SUPPORTED; } audio_config_t nConfig; @@ -2809,7 +2809,7 @@ static jint android_media_AudioSystem_getDirectPlaybackSupport(JNIEnv *env, jobj status_t status = AudioSystem::getDirectPlaybackSupport(paa.get(), &nConfig, &directMode); if (status != NO_ERROR) { ALOGW("%s native returned error %d", __func__, status); - return AUDIO_DIRECT_NOT_SUPPORTED; + return DIRECT_NOT_SUPPORTED; } return convertAudioDirectModeFromNative(directMode); } diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 0eb8c6a3ecf3..011e0514b82f 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -83,17 +83,20 @@ static void init() { constexpr int INDIC_MIN_PREFIX = 2; constexpr int INDIC_MIN_SUFFIX = 2; + addHyphenator("af", 1, 1); // Afrikaans addHyphenator("am", 1, 1); // Amharic addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese addHyphenator("be", 2, 2); // Belarusian addHyphenator("bg", 2, 2); // Bulgarian addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali + addHyphenator("cs", 2, 2); // Czech addHyphenator("cu", 1, 2); // Church Slavonic addHyphenator("cy", 2, 3); // Welsh addHyphenator("da", 2, 2); // Danish addHyphenator("de-1901", 2, 2); // German 1901 orthography addHyphenator("de-1996", 2, 2); // German 1996 orthography addHyphenator("de-CH-1901", 2, 2); // Swiss High German 1901 orthography + addHyphenator("el", 1, 1); // Greek addHyphenator("en-GB", 2, 3); // British English addHyphenator("en-US", 2, 3); // American English addHyphenator("es", 2, 2); // Spanish @@ -110,18 +113,23 @@ static void init() { // Going with a more conservative value of (2, 2) for now. addHyphenator("hy", 2, 2); // Armenian addHyphenator("it", 2, 2); // Italian + addHyphenator("ka", 1, 2); // Georgian addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada addHyphenator("la", 2, 2); // Latin addHyphenator("lt", 2, 2); // Lithuanian + addHyphenator("lv", 2, 2); // Latvian addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam addHyphenator("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi addHyphenator("nb", 2, 2); // Norwegian Bokmål + addHyphenator("nl", 2, 2); // Dutch addHyphenator("nn", 2, 2); // Norwegian Nynorsk addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi addHyphenator("pt", 2, 3); // Portuguese + addHyphenator("sk", 2, 2); // Slovak addHyphenator("sl", 2, 2); // Slovenian + addHyphenator("sq", 2, 2); // Albanian addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu addHyphenator("tk", 2, 2); // Turkmen diff --git a/core/proto/android/inputmethodservice/softinputwindow.proto b/core/proto/android/inputmethodservice/softinputwindow.proto index 85b7d7382805..e0ba6bf33567 100644 --- a/core/proto/android/inputmethodservice/softinputwindow.proto +++ b/core/proto/android/inputmethodservice/softinputwindow.proto @@ -23,10 +23,10 @@ package android.inputmethodservice; option java_multiple_files = true; message SoftInputWindowProto { - optional string name = 1; - optional int32 window_type = 2; - optional int32 gravity = 3; - optional bool takes_focus = 4; + reserved 1; // name + reserved 2; // window_type + reserved 3; // gravity + reserved 4; // takes_focus optional .android.graphics.RectProto bounds = 5; optional int32 window_state = 6; }
\ No newline at end of file diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index ba4a5b0ce222..5090d8cb5c3a 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -85,6 +85,7 @@ message SecureSettingsProto { optional SettingProto accessibility_floating_menu_icon_type = 39 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_floating_menu_opacity = 40 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_floating_menu_fade_enabled = 41 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto odi_captions_volume_ui_enabled = 42 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index 48feb4dd958f..1dedbb9362a3 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -112,6 +112,8 @@ message PackageProto { optional string last_disabled_app_caller = 8; repeated string suspending_package = 9; optional int32 distraction_flags = 10; + // UTC timestamp of first install for the user + optional int32 first_install_time_ms = 11; } message InstallSourceProto { @@ -147,8 +149,7 @@ message PackageProto { optional int32 version_code = 3; // Package's reported version string (what's displayed to the user). optional string version_string = 4; - // UTC timestamp of install - optional int64 install_time_ms = 5; + reserved 5; // Millisecond UTC timestamp of latest update adjusted to Google's server clock. optional int64 update_time_ms = 6; // From "dumpsys package" - name of package which installed this one. diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto index 1b9aff989cc8..b1ed365309c4 100644 --- a/core/proto/android/view/imeinsetssourceconsumer.proto +++ b/core/proto/android/view/imeinsetssourceconsumer.proto @@ -16,7 +16,6 @@ syntax = "proto2"; -import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto"; package android.view; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d0fee11c5f26..a005eb0d5f03 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -714,6 +714,9 @@ <protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" /> <protected-broadcast android:name="android.app.action.SHOW_NEW_USER_DISCLAIMER" /> + <!-- Added in T --> + <protected-broadcast android:name="android.intent.action.REFRESH_SAFETY_SOURCES" /> + <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> <!-- ====================================================================== --> @@ -1499,7 +1502,7 @@ <p>Protection level: dangerous <p> This is a hard restricted permission which cannot be held by an app until - the installer on record whitelists the permission. For more details see + the installer on record allowlists the permission. For more details see {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. --> <permission android:name="android.permission.BODY_SENSORS_BACKGROUND" @@ -5479,6 +5482,16 @@ android:protectionLevel="signature|installer" /> <!-- + @SystemApi + Allows the holder to start the screen to review permission decisions. + <p>Protection level: signature|installer + @hide --> + <permission android:name="android.permission.START_REVIEW_PERMISSION_DECISIONS" + android:label="@string/permlab_startReviewPermissionDecisions" + android:description="@string/permdesc_startReviewPermissionDecisions" + android:protectionLevel="signature|installer" /> + + <!-- Allows the holder to start the screen with a list of app features. <p>Protection level: signature|installer --> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index bfc1c83e8fc5..1705371a7f4a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8440,6 +8440,8 @@ <!-- Component name of an activity that allows the user to modify the settings for this dream. --> <attr name="settingsActivity" /> + <!-- A preview, in a drawable resource id, of what the Dream will look like. --> + <attr name="previewImage" format="reference" /> </declare-styleable> <!-- Use <code>trust-agent</code> as the root tag of the XML resource that diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 06f347f4daff..db24475cf780 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3070,7 +3070,6 @@ <declare-styleable name="AndroidManifestMetaData" parent="AndroidManifestApplication AndroidManifestActivity - AndroidManifestApexSystemService AndroidManifestReceiver AndroidManifestProvider AndroidManifestService @@ -3105,7 +3104,6 @@ <declare-styleable name="AndroidManifestProperty" parent="AndroidManifestApplication AndroidManifestActivity - AndroidManifestApexSystemService AndroidManifestReceiver AndroidManifestProvider AndroidManifestService"> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index bd0604e03fee..7d8bceaf89e9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2897,6 +2897,10 @@ <!-- Make the IME killable by the lowmemorykiller by raising its oom_score_adj. --> <bool name="config_killableInputMethods">false</bool> + <!-- Prevent the InputMethodManagerService from starting up the IME unless + the currently focused view is a text editor. --> + <bool name="config_preventImeStartupUnlessTextEditor">false</bool> + <!-- The list of classes that should be added to the notification ranking pipeline. See {@link com.android.server.notification.NotificationSignalExtractor} If you add a new extractor to this list make sure to update diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index bc127d9c1d06..7d44fd99fb2e 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3281,6 +3281,8 @@ </staging-public-group> <staging-public-group type="array" first-id="0x01d90000"> + <!-- @hide @SystemApi --> + <public name="config_optionalIpSecAlgorithms" /> </staging-public-group> <staging-public-group type="drawable" first-id="0x01d80000"> @@ -3313,6 +3315,8 @@ <staging-public-group type="bool" first-id="0x01cf0000"> <!-- @hide @SystemApi --> <public name="config_systemCaptionsServiceCallsEnabled" /> + <!-- @hide @TestApi --> + <public name="config_preventImeStartupUnlessTextEditor" /> </staging-public-group> <staging-public-group type="fraction" first-id="0x01ce0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1f5f18914daa..2879759db521 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2012,6 +2012,11 @@ <string name="permdesc_startViewPermissionUsage">Allows the holder to start the permission usage for an app. Should never be needed for normal apps.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permlab_startReviewPermissionDecisions">start view permission decisions</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permdesc_startReviewPermissionDecisions">Allows the holder to start screen to review permission decisions. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> <string name="permlab_startViewAppFeatures">start view app features</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> <string name="permdesc_startViewAppFeatures">Allows the holder to start viewing the features info for an app.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7687b93cf760..ba4aa81766e0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2250,6 +2250,7 @@ <java-symbol type="bool" name="config_autoResetAirplaneMode" /> <java-symbol type="string" name="config_notificationAccessConfirmationActivity" /> <java-symbol type="bool" name="config_killableInputMethods" /> + <java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" /> <java-symbol type="layout" name="resolver_list" /> <java-symbol type="id" name="resolver_list" /> diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java index 2c3c1ed0c40e..9bb064c447b8 100644 --- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java +++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java @@ -25,6 +25,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; +import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; import androidx.test.filters.LargeTest; @@ -39,6 +40,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +@Presubmit @LargeTest public class ApplicationPackageManagerTest extends TestCase { private static final String sInternalVolPath = "/data"; diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 5db6a3e421d5..bfb2fd57975f 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -273,13 +273,14 @@ public class ActivityThreadTest { newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; newerConfig.seq = seq + 2; - final ActivityClientRecord r = getActivityClientRecord(activity); - activityThread.updatePendingActivityConfiguration(r, newerConfig); + activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), + newerConfig); final Configuration olderConfig = new Configuration(); olderConfig.orientation = orientation; olderConfig.seq = seq + 1; + final ActivityClientRecord r = getActivityClientRecord(activity); activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY); assertEquals(numOfConfig, activity.mNumOfConfigChanges); assertEquals(olderConfig.orientation, activity.mConfig.orientation); @@ -504,7 +505,8 @@ public class ActivityThreadTest { ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; final ActivityClientRecord r = getActivityClientRecord(activity); - activityThread.updatePendingActivityConfiguration(r, newActivityConfig); + activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), + newActivityConfig); activityThread.handleActivityConfigurationChanged(r, newActivityConfig, INVALID_DISPLAY); diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java index ceebc621f51f..c9a6d222dcba 100644 --- a/core/tests/coretests/src/android/content/ContentProviderTest.java +++ b/core/tests/coretests/src/android/content/ContentProviderTest.java @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ProviderInfo; import android.net.Uri; import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; @@ -32,6 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; +@Presubmit @RunWith(AndroidJUnit4.class) public class ContentProviderTest { diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index 01e240a7ff8a..7b70b412e62b 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -35,6 +35,7 @@ import android.net.Uri; import android.os.MemoryFile; import android.os.ParcelFileDescriptor; import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; import android.util.Size; import androidx.test.InstrumentationRegistry; @@ -45,6 +46,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +@Presubmit @RunWith(AndroidJUnit4.class) public class ContentResolverTest { diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java index 3d7d807ca53d..e4a9ce59911b 100644 --- a/core/tests/coretests/src/android/content/ContextTest.java +++ b/core/tests/coretests/src/android/content/ContextTest.java @@ -34,6 +34,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.ImageReader; import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; import android.view.Display; import androidx.test.core.app.ApplicationProvider; @@ -48,6 +49,7 @@ import org.junit.runner.RunWith; * Build/Install/Run: * atest FrameworksCoreTests:ContextTest */ +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class ContextTest { diff --git a/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java b/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java index 1bc46a79f1c0..68d4cd4a97e7 100644 --- a/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ManagedUserContentResolverTest.java @@ -20,6 +20,7 @@ import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Presubmit; import androidx.test.filters.LargeTest; @@ -36,6 +37,7 @@ import androidx.test.filters.LargeTest; * Run: adb shell am instrument -e class android.content.ManagedUserContentResolverTest -w \ * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner */ +@Presubmit @LargeTest public class ManagedUserContentResolverTest extends AbstractCrossUserContentResolverTest { @Override diff --git a/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java b/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java index dbe027800e3f..de4c5725c190 100644 --- a/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java +++ b/core/tests/coretests/src/android/content/SecondaryUserContentResolverTest.java @@ -18,6 +18,7 @@ package android.content; import android.content.pm.UserInfo; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; import androidx.test.filters.LargeTest; @@ -34,6 +35,7 @@ import androidx.test.filters.LargeTest; * Run: adb shell am instrument -e class android.content.SecondaryUserContentResolverTest -w \ * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner */ +@Presubmit @LargeTest public class SecondaryUserContentResolverTest extends AbstractCrossUserContentResolverTest { @Override diff --git a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt index 121caef87f6f..3c8f90c9c0f8 100644 --- a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt +++ b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt @@ -32,14 +32,14 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue private const val TEST_IMSI1 = "TESTIMSI1" -private const val TEST_SSID1 = "TESTISSID1" +private const val TEST_WIFI_NETWORK_KEY1 = "TESTKEY1" @RunWith(AndroidJUnit4::class) class NetworkPolicyTest { @Test fun testTemplateBackupRestore() { assertPolicyBackupRestore(createTestPolicyForTemplate( - NetworkTemplate.buildTemplateWifi(TEST_SSID1))) + NetworkTemplate.buildTemplateWifi(TEST_WIFI_NETWORK_KEY1))) assertPolicyBackupRestore(createTestPolicyForTemplate( NetworkTemplate.buildTemplateMobileAll(TEST_IMSI1))) assertPolicyBackupRestore(createTestPolicyForTemplate( @@ -79,6 +79,6 @@ class NetworkPolicyTest { // Verify wifi template can be persistable if the Wifi Network Key is supplied. assertTrue(NetworkPolicy.isTemplatePersistable(NetworkTemplate.Builder(MATCH_WIFI) - .setWifiNetworkKey(TEST_SSID1).build())) + .setWifiNetworkKeys(setOf(TEST_WIFI_NETWORK_KEY1)).build())) } -}
\ No newline at end of file +} diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java index 7ccbb0150cb6..e6660f32f817 100644 --- a/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java +++ b/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java @@ -170,7 +170,7 @@ public class StorageManagerBaseTest extends InstrumentationTestCase { when(mFile.getUsableSpace()).thenReturn(10000L); when(mFile.getTotalSpace()).thenReturn(100000L); long result = mSm.getStorageCacheBytes(mFile, 0); - assertThat(result).isEqualTo(4666L); + assertThat(result).isEqualTo(4667L); } /** diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java new file mode 100644 index 000000000000..8f044616e323 --- /dev/null +++ b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java @@ -0,0 +1,261 @@ +/* + * 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.view; + +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_UP; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Instrumentation; +import android.content.Context; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link HandwritingInitiator} + * + * Build/Install/Run: + * atest FrameworksCoreTests:HandwritingInitiatorTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class HandwritingInitiatorTest { + private static final int TOUCH_SLOP = 8; + private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); + private static final Rect sHwArea = new Rect(100, 200, 500, 500); + private static final EditorInfo sFakeEditorInfo = new EditorInfo(); + + private HandwritingInitiator mHandwritingInitiator; + private View mTestView; + + @Before + public void setup() { + final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); + Context context = mInstrumentation.getTargetContext(); + ViewConfiguration viewConfiguration = mock(ViewConfiguration.class); + when(viewConfiguration.getScaledTouchSlop()).thenReturn(TOUCH_SLOP); + + InputMethodManager inputMethodManager = context.getSystemService(InputMethodManager.class); + mHandwritingInitiator = + spy(new HandwritingInitiator(viewConfiguration, inputMethodManager)); + mHandwritingInitiator.updateEditorBound(sHwArea); + + // mock a parent so that HandwritingInitiator can get + ViewGroup parent = new ViewGroup(context) { + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // We don't layout this view. + } + @Override + public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { + r.left = sHwArea.left; + r.top = sHwArea.top; + r.right = sHwArea.right; + r.bottom = sHwArea.bottom; + return true; + } + }; + + mTestView = mock(View.class); + when(mTestView.isAttachedToWindow()).thenReturn(true); + parent.addView(mTestView); + } + + @Test + public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + final int x1 = (sHwArea.left + sHwArea.right) / 2; + final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + TOUCH_SLOP * 2; + final int y2 = y1; + + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + // Stylus movement win HandwritingArea should trigger IMM.startHandwriting once. + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + } + + @Test + public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() { + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + final int x1 = (sHwArea.left + sHwArea.right) / 2; + final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + TOUCH_SLOP * 2; + final int y2 = y1; + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + + final int x3 = x2 + TOUCH_SLOP * 2; + final int y3 = y2; + MotionEvent stylusEvent3 = createStylusEvent(ACTION_MOVE, x3, y3, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent3); + + MotionEvent stylusEvent4 = createStylusEvent(ACTION_UP, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent4); + + // It only calls startHandwriting once for each ACTION_DOWN. + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + } + + @Test + public void onTouchEvent_startHandwriting_inputConnectionBuiltAfterStylusMove() { + final int x1 = (sHwArea.left + sHwArea.right) / 2; + final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + TOUCH_SLOP * 2; + final int y2 = y1; + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + // InputConnection is created after stylus movement. + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + } + + @Test + public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + final int x1 = 200; + final int y1 = 200; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + TOUCH_SLOP / 2; + final int y2 = y1; + MotionEvent stylusEvent2 = createStylusEvent(ACTION_UP, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + } + + @Test + public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() { + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + final int x1 = 10; + final int y1 = 10; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + TOUCH_SLOP * 2; + final int y2 = y1; + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + } + + @Test + public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTapTimeOut() { + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + final int x1 = 10; + final int y1 = 10; + final long time1 = 10L; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + TOUCH_SLOP * 2; + final int y2 = y1; + final long time2 = time1 + TAP_TIMEOUT + 10L; + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, time2); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + // stylus movement is after TAP_TIMEOUT it shouldn't call startHandwriting. + verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + } + + @Test + public void onInputConnectionCreated_inputConnectionCreated() { + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); + assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); + } + + @Test + public void onInputConnectionCreated_inputConnectionClosed() { + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionClosed(mTestView); + + assertThat(mHandwritingInitiator.mConnectedView).isNull(); + assertThat(mHandwritingInitiator.mEditorBound).isNull(); + } + + @Test + public void onInputConnectionCreated_inputConnectionRestarted() { + // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be + // called before View#onInputConnectionClosedInternal. As a result, we need to handle the + // case where "one view "2 InputConnections". + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionClosed(mTestView); + + assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); + assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); + } + + @Test + public void updateEditorBound() { + Rect rect = new Rect(1, 2, 3, 4); + mHandwritingInitiator.updateEditorBound(rect); + + assertThat(mHandwritingInitiator.mEditorBound).isEqualTo(rect); + } + + private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) { + MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); + properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS; + + MotionEvent.PointerCoords[] coords = MotionEvent.PointerCoords.createArray(1); + coords[0].x = x; + coords[0].y = y; + + return MotionEvent.obtain(0 /* downTime */, eventTime /* eventTime */, action, 1, + properties, coords, 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, + 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, + InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */); + } +} diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java index c4c983d24af9..78a8f7b3f32e 100644 --- a/core/tests/coretests/src/android/view/MotionEventTest.java +++ b/core/tests/coretests/src/android/view/MotionEventTest.java @@ -16,7 +16,6 @@ package android.view; -import static android.view.InputDevice.SOURCE_CLASS_POINTER; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.TOOL_TYPE_FINGER; @@ -215,27 +214,4 @@ public class MotionEventTest { rotInvalid.transform(mat); assertEquals(-1, rotInvalid.getSurfaceRotation()); } - - @Test - public void testUsesPointerSourceByDefault() { - final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, - ACTION_DOWN, 0 /* x */, 0 /* y */, 0 /* metaState */); - assertTrue(event.isFromSource(SOURCE_CLASS_POINTER)); - } - - @Test - public void testLocationOffsetOnlyAppliedToNonPointerSources() { - final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, - ACTION_DOWN, 10 /* x */, 20 /* y */, 0 /* metaState */); - event.offsetLocation(40, 50); - - // The offset should be applied since a pointer source is used by default. - assertEquals(50, (int) event.getX()); - assertEquals(70, (int) event.getY()); - - // The offset should not be applied if the source is changed to a non-pointer source. - event.setSource(InputDevice.SOURCE_JOYSTICK); - assertEquals(10, (int) event.getX()); - assertEquals(20, (int) event.getY()); - } } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java index e689b5d33107..dd8cc6e0dd03 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java @@ -26,6 +26,8 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; @@ -997,6 +999,60 @@ public class AccessibilityCacheTest { } } + @Test + public void enable_cacheEnabled() { + mAccessibilityCache.setEnabled(false); + assertFalse(mAccessibilityCache.isEnabled()); + + mAccessibilityCache.setEnabled(true); + assertTrue(mAccessibilityCache.isEnabled()); + } + + @Test + public void disable_cacheDisabled() { + mAccessibilityCache.setEnabled(false); + assertFalse(mAccessibilityCache.isEnabled()); + } + + @Test + public void queryNode_nodeIsInCache() { + AccessibilityNodeInfo info = new AccessibilityNodeInfo(); + mAccessibilityCache.add(info); + + assertTrue(mAccessibilityCache.isNodeInCache(info)); + } + + @Test + public void clearSubtreeWithNode_nodeInCacheInvalidated() { + AccessibilityNodeInfo info = new AccessibilityNodeInfo(); + info.setSource(getMockViewWithA11yAndWindowIds(1, 1)); + mAccessibilityCache.add(info); + + mAccessibilityCache.clearSubTree(info); + assertFalse(mAccessibilityCache.isNodeInCache(info)); + } + + @Test + public void clearSubtreeWithNode_subtreeInCacheInvalidated() { + AccessibilityNodeInfo info = new AccessibilityNodeInfo(); + View parentView = getMockViewWithA11yAndWindowIds(1, 1); + info.setSource(parentView); + + AccessibilityNodeInfo childInfo = new AccessibilityNodeInfo(); + View childView = getMockViewWithA11yAndWindowIds(2, 1); + childInfo.setSource(childView); + + childInfo.setParent(parentView); + info.addChild(childView); + mAccessibilityCache.add(info); + mAccessibilityCache.add(childInfo); + + mAccessibilityCache.clearSubTree(info); + + assertFalse(mAccessibilityCache.isNodeInCache(info)); + assertFalse(mAccessibilityCache.isNodeInCache(childInfo)); + } + private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) { AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain(); windowInfo.setId(windowId); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java index 2c8c38532d10..6df9002608af 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java @@ -42,14 +42,14 @@ public class AccessibilityEventTest { // and assertAccessibilityEventCleared /** The number of properties of the {@link AccessibilityEvent} class. */ - private static final int A11Y_EVENT_NON_STATIC_FIELD_COUNT = 34; + private static final int A11Y_EVENT_NON_STATIC_FIELD_COUNT = 32; // The number of fields tested in the corresponding CTS AccessibilityRecordTest: // assertAccessibilityRecordCleared, fullyPopulateAccessibilityRecord, // and assertEqualAccessibilityRecord /** The number of properties of the {@link AccessibilityRecord} class. */ - private static final int A11Y_RECORD_NON_STATIC_FIELD_COUNT = 25; + private static final int A11Y_RECORD_NON_STATIC_FIELD_COUNT = 23; @Test public void testImportantForAccessibiity_getSetWorkAcrossParceling() { diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index 212fdcace6ac..bb1a3b182f91 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -17,7 +17,6 @@ package android.view.accessibility; import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertSame; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -188,17 +187,6 @@ public class AccessibilityManagerTest { } @Test - public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception { - AccessibilityEvent sentEvent = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_ANNOUNCEMENT); - - AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); - manager.sendAccessibilityEvent(sentEvent); - - assertSame("The event should be recycled.", sentEvent, AccessibilityEvent.obtain()); - } - - @Test public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception { AccessibilityEvent sentEvent = AccessibilityEvent.obtain(); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index ad1f298e57a1..62d0b2e0b52f 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -173,6 +173,8 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void setFocusAppearance(int strokeWidth, int color) {} + public void setCacheEnabled(boolean enabled) {} + public void logTrace(long timestamp, String where, String callingParams, int processId, long threadId, int callingUid, Bundle callingStack) {} diff --git a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java deleted file mode 100644 index 11f4e3cf9304..000000000000 --- a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.accessibility; - -import androidx.test.filters.SmallTest; - -import junit.framework.TestCase; - -/** - * This class exercises the caching and recycling of {@link AccessibilityEvent}s. - */ -public class RecycleAccessibilityEventTest extends TestCase { - - private static final String CLASS_NAME = "foo.bar.baz.Test"; - private static final String PACKAGE_NAME = "foo.bar.baz"; - private static final String TEXT = "Some stuff"; - - private static final String CONTENT_DESCRIPTION = "Content description"; - private static final int ITEM_COUNT = 10; - private static final int CURRENT_ITEM_INDEX = 1; - - private static final int FROM_INDEX = 1; - private static final int ADDED_COUNT = 2; - private static final int REMOVED_COUNT = 1; - - /** - * If an {@link AccessibilityEvent} is marshaled/unmarshaled correctly - */ - @SmallTest - public void testAccessibilityEventViewTextChangedType() { - AccessibilityEvent first = - AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); - assertNotNull(first); - - first.setClassName(CLASS_NAME); - first.setPackageName(PACKAGE_NAME); - first.getText().add(TEXT); - first.setFromIndex(FROM_INDEX); - first.setAddedCount(ADDED_COUNT); - first.setRemovedCount(REMOVED_COUNT); - first.setChecked(true); - first.setContentDescription(CONTENT_DESCRIPTION); - first.setItemCount(ITEM_COUNT); - first.setCurrentItemIndex(CURRENT_ITEM_INDEX); - first.setEnabled(true); - first.setPassword(true); - - first.recycle(); - - assertNotNull(first); - assertNull(first.getClassName()); - assertNull(first.getPackageName()); - assertEquals(0, first.getText().size()); - assertFalse(first.isChecked()); - assertNull(first.getContentDescription()); - assertEquals(-1, first.getItemCount()); - assertEquals(AccessibilityEvent.INVALID_POSITION, first.getCurrentItemIndex()); - assertFalse(first.isEnabled()); - assertFalse(first.isPassword()); - assertEquals(-1, first.getFromIndex()); - assertEquals(-1, first.getAddedCount()); - assertEquals(-1, first.getRemovedCount()); - - // get another event from the pool (this must be the recycled first) - AccessibilityEvent second = AccessibilityEvent.obtain(); - assertEquals(first, second); - } -} diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java index 9696fdfc462b..4f95cb88e217 100644 --- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java +++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java @@ -28,7 +28,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withTagValue; import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static com.android.internal.widget.FloatingToolbarPopup.MenuItemRepr; +import static com.android.internal.widget.floatingtoolbar.LocalFloatingToolbarPopup.MenuItemRepr; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; @@ -42,7 +42,7 @@ import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewInteraction; -import com.android.internal.widget.FloatingToolbar; +import com.android.internal.widget.floatingtoolbar.FloatingToolbar; import org.hamcrest.Description; import org.hamcrest.Matcher; diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index c0ced6c9ecdb..69ff7c636934 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -180,6 +180,16 @@ public class ChooserActivityTest { return clientIntent; } + /** + * Whether {@code #testIsAppPredictionServiceAvailable} should verify the behavior after + * changing the availability conditions at runtime. In the unbundled chooser, the availability + * is cached at start and will never be re-evaluated. + * TODO: remove when we no longer want to test the system's on-the-fly evaluation. + */ + protected boolean shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime() { + return true; + } + /* -------- * The code in this section is unorthodox and can be simplified/reverted when we no longer need * to support the parallel chooser implementations. @@ -784,7 +794,8 @@ public class ChooserActivityTest { assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); assertThat(logger.get(1).mimeType, is("text/plain")); - assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).packageName, is( + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName())); assertThat(logger.get(1).appProvidedApp, is(0)); assertThat(logger.get(1).appProvidedDirect, is(0)); assertThat(logger.get(1).isWorkprofile, is(false)); @@ -802,7 +813,7 @@ public class ChooserActivityTest { assertThat(logger.event(4).getId(), is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId())); - // SHARESHEET_EDIT_TARGET_SELECTED: + // SHARESHEET_NEARBY_TARGET_SELECTED: assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED)); assertThat(logger.get(5).targetType, is(ChooserActivityLogger @@ -814,7 +825,7 @@ public class ChooserActivityTest { - @Test + @Test @Ignore public void testEditImageLogs() throws Exception { Intent sendIntent = createSendImageIntent( Uri.parse("android.resource://com.android.frameworks.coretests/" @@ -853,7 +864,8 @@ public class ChooserActivityTest { assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); assertThat(logger.get(1).mimeType, is("image/png")); - assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).packageName, is( + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName())); assertThat(logger.get(1).appProvidedApp, is(0)); assertThat(logger.get(1).appProvidedDirect, is(0)); assertThat(logger.get(1).isWorkprofile, is(false)); @@ -1321,6 +1333,10 @@ public class ChooserActivityTest { } else { assertThat(activity.isAppPredictionServiceAvailable(), is(true)); + if (!shouldTestTogglingAppPredictionServiceAvailabilityAtRuntime()) { + return; + } + ChooserActivityOverrideData.getInstance().resources = Mockito.spy(activity.getResources()); when( @@ -2101,7 +2117,8 @@ public class ChooserActivityTest { assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); assertThat(logger.get(1).mimeType, is("text/plain")); - assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).packageName, is( + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName())); assertThat(logger.get(1).appProvidedApp, is(0)); assertThat(logger.get(1).appProvidedDirect, is(0)); assertThat(logger.get(1).isWorkprofile, is(false)); @@ -2119,7 +2136,7 @@ public class ChooserActivityTest { assertThat(logger.event(4).getId(), is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId())); - // SHARESHEET_EDIT_TARGET_SELECTED: + // SHARESHEET_APP_TARGET_SELECTED: assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED)); assertThat(logger.get(5).targetType, is(ChooserActivityLogger @@ -2197,7 +2214,8 @@ public class ChooserActivityTest { assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); assertThat(logger.get(1).mimeType, is("text/plain")); - assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).packageName, is( + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName())); assertThat(logger.get(1).appProvidedApp, is(0)); assertThat(logger.get(1).appProvidedDirect, is(0)); assertThat(logger.get(1).isWorkprofile, is(false)); @@ -2215,7 +2233,7 @@ public class ChooserActivityTest { .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId())); } - @Test + @Test @Ignore public void testEmptyDirectRowLogging() throws InterruptedException { Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed @@ -2259,7 +2277,8 @@ public class ChooserActivityTest { assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); assertThat(logger.get(1).mimeType, is("text/plain")); - assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).packageName, is( + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName())); assertThat(logger.get(1).appProvidedApp, is(0)); assertThat(logger.get(1).appProvidedDirect, is(0)); assertThat(logger.get(1).isWorkprofile, is(false)); @@ -2320,7 +2339,8 @@ public class ChooserActivityTest { assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); assertThat(logger.get(1).mimeType, is("text/plain")); - assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).packageName, is( + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName())); assertThat(logger.get(1).appProvidedApp, is(0)); assertThat(logger.get(1).appProvidedDirect, is(0)); assertThat(logger.get(1).isWorkprofile, is(false)); @@ -2338,7 +2358,7 @@ public class ChooserActivityTest { assertThat(logger.event(4).getId(), is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId())); - // SHARESHEET_EDIT_TARGET_SELECTED: + // SHARESHEET_COPY_TARGET_SELECTED: assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED)); assertThat(logger.get(5).targetType, is(ChooserActivityLogger @@ -2386,7 +2406,8 @@ public class ChooserActivityTest { assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED)); assertThat(logger.get(1).intent, is(Intent.ACTION_SEND)); assertThat(logger.get(1).mimeType, is(TEST_MIME_TYPE)); - assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests")); + assertThat(logger.get(1).packageName, is( + InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName())); assertThat(logger.get(1).appProvidedApp, is(0)); assertThat(logger.get(1).appProvidedDirect, is(0)); assertThat(logger.get(1).isWorkprofile, is(false)); diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java index aea453ea4e57..caec3651210a 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import android.os.FileUtils; import android.os.SystemProperties; +import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -46,6 +47,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +@Presubmit @RunWith(AndroidJUnit4.class) public class OverlayConfigTest { private static final String TEST_APK_PACKAGE_NAME = diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java index 7db31fb0ace5..9b3876f6d4ca 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -39,6 +39,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import libcore.testing.io.TestIoUtils; + import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,9 +54,12 @@ public class BatteryUsageStatsProviderTest { private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; private static final long MINUTE_IN_MS = 60 * 1000; + private final File mHistoryDir = + TestIoUtils.createTemporaryDirectory(getClass().getSimpleName()); @Rule - public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(12345) - .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0); + public final BatteryUsageStatsRule mStatsRule = + new BatteryUsageStatsRule(12345, mHistoryDir) + .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0); @Test public void test_getBatteryUsageStats() { diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java index f75a6df0a325..b3056e222477 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java @@ -40,6 +40,7 @@ import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.mockito.stubbing.Answer; +import java.io.File; import java.util.Arrays; public class BatteryUsageStatsRule implements TestRule { @@ -57,14 +58,18 @@ public class BatteryUsageStatsRule implements TestRule { private boolean mScreenOn; public BatteryUsageStatsRule() { - this(0); + this(0, null); } public BatteryUsageStatsRule(long currentTime) { + this(currentTime, null); + } + + public BatteryUsageStatsRule(long currentTime, File historyDir) { Context context = InstrumentationRegistry.getContext(); mPowerProfile = spy(new PowerProfile(context, true /* forTest */)); mMockClock.currentTime = currentTime; - mBatteryStats = new MockBatteryStatsImpl(mMockClock); + mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir); mBatteryStats.setPowerProfile(mPowerProfile); mBatteryStats.onSystemReady(); } diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 8d3eadb8496d..a9e730d58e65 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -27,8 +27,6 @@ public final class BLASTBufferQueue { // Note: This field is accessed by native code. public long mNativeObject; // BLASTBufferQueue* - private static native long nativeCreateAndUpdate(String name, long surfaceControl, long width, - long height, int format); private static native long nativeCreate(String name); private static native void nativeDestroy(long ptr); private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle); @@ -40,11 +38,13 @@ public final class BLASTBufferQueue { long frameNumber); private static native long nativeGetLastAcquiredFrameNum(long ptr); private static native void nativeApplyPendingTransactions(long ptr, long frameNumber); + private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr); /** Create a new connection with the surface flinger. */ public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, @PixelFormat.Format int format) { - mNativeObject = nativeCreateAndUpdate(name, sc.mNativeObject, width, height, format); + this(name); + update(sc, width, height, format); } public BLASTBufferQueue(String name) { @@ -152,4 +152,11 @@ public final class BLASTBufferQueue { public long getLastAcquiredFrameNum() { return nativeGetLastAcquiredFrameNum(mNativeObject); } + + /** + * @return True if the associated SurfaceControl has the same handle as {@param sc}. + */ + public boolean isSameSurfaceControl(SurfaceControl sc) { + return nativeIsSameSurfaceControl(mNativeObject, sc.mNativeObject); + } } diff --git a/graphics/java/android/graphics/RenderEffect.java b/graphics/java/android/graphics/RenderEffect.java index ad4c3fe86175..b8a46856601e 100644 --- a/graphics/java/android/graphics/RenderEffect.java +++ b/graphics/java/android/graphics/RenderEffect.java @@ -290,6 +290,22 @@ public final class RenderEffect { return new RenderEffect(nativeCreateShaderEffect(shader.getNativeInstance())); } + /** + * Create a {@link RenderEffect} that executes the provided {@link RuntimeShader} and passes + * the contents of the {@link android.graphics.RenderNode} that this RenderEffect is installed + * on as an input to the shader. + * @param shader the runtime shader that will bind the inputShaderName to the RenderEffect input + * @param uniformShaderName the uniform name defined in the RuntimeShader's program to which + * the contents of the RenderNode will be bound + */ + @NonNull + public static RenderEffect createRuntimeShaderEffect( + @NonNull RuntimeShader shader, @NonNull String uniformShaderName) { + return new RenderEffect( + nativeCreateRuntimeShaderEffect(shader.getNativeShaderBuilder(), + uniformShaderName)); + } + private final long mNativeRenderEffect; /* only constructed from static factory methods */ @@ -318,5 +334,7 @@ public final class RenderEffect { private static native long nativeCreateBlendModeEffect(long dst, long src, int blendmode); private static native long nativeCreateChainEffect(long outer, long inner); private static native long nativeCreateShaderEffect(long shader); + private static native long nativeCreateRuntimeShaderEffect( + long shaderBuilder, String inputShaderName); private static native long nativeGetFinalizer(); } diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index cdff5858c77d..e9b3c4990ef2 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -43,7 +43,7 @@ filegroup { name: "wm_shell_util-sources", srcs: [ "src/com/android/wm/shell/util/**/*.java", - "src/com/android/wm/shell/common/split/SplitScreenConstants.java" + "src/com/android/wm/shell/common/split/SplitScreenConstants.java", ], path: "src", } @@ -74,13 +74,13 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) transform-protolog-calls " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + - "--protolog-impl-class com.android.wm.shell.protolog.ShellProtoLogImpl " + - "--protolog-cache-class com.android.wm.shell.protolog.ShellProtoLogCache " + - "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + - "--loggroups-jar $(location :wm_shell_protolog-groups) " + - "--output-srcjar $(out) " + - "$(locations :wm_shell-sources)", + "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-impl-class com.android.wm.shell.protolog.ShellProtoLogImpl " + + "--protolog-cache-class com.android.wm.shell.protolog.ShellProtoLogCache " + + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + + "--loggroups-jar $(location :wm_shell_protolog-groups) " + + "--output-srcjar $(out) " + + "$(locations :wm_shell-sources)", out: ["wm_shell_protolog.srcjar"], } @@ -92,13 +92,14 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + - "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + - "--loggroups-jar $(location :wm_shell_protolog-groups) " + - "--viewer-conf $(out) " + - "$(locations :wm_shell-sources)", + "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + + "--loggroups-jar $(location :wm_shell_protolog-groups) " + + "--viewer-conf $(out) " + + "$(locations :wm_shell-sources)", out: ["wm_shell_protolog.json"], } + // End ProtoLog java_library { @@ -123,11 +124,12 @@ android_library { "res", ], java_resources: [ - ":generate-wm_shell_protolog.json" + ":generate-wm_shell_protolog.json", ], static_libs: [ "androidx.appcompat_appcompat", "androidx.arch.core_core-runtime", + "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", "androidx.recyclerview_recyclerview", "kotlinx-coroutines-android", diff --git a/libs/WindowManager/Shell/res/layout/badged_image_view.xml b/libs/WindowManager/Shell/res/layout/badged_image_view.xml new file mode 100644 index 000000000000..5f07121ec7d3 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/badged_image_view.xml @@ -0,0 +1,55 @@ +<?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. + --> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <ImageView + android:id="@+id/icon_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@null" /> + + <!-- + Icon badge size is defined in Launcher3 BaseIconFactory as 0.444 of icon size. + Constraint guide starts from left, which means for a badge positioned on the right, + percent has to be 1 - 0.444 to have the same effect. + --> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/app_icon_constraint_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.556" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/app_icon_constraint_vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.556" /> + + <ImageView + android:id="@+id/app_icon_view" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@null" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="@id/app_icon_constraint_vertical" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="@id/app_icon_constraint_horizontal" /> + +</merge>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index 686fbbfd6f7c..c52d87dde07f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -19,7 +19,6 @@ import android.annotation.DrawableRes; import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Path; @@ -27,14 +26,16 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.PathParser; -import android.view.Gravity; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewOutlineProvider; -import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.constraintlayout.widget.ConstraintLayout; + import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.IconNormalizer; +import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import java.util.EnumSet; @@ -46,14 +47,12 @@ import java.util.EnumSet; * Badge = the icon associated with the app that created this bubble, this will show work profile * badge if appropriate. */ -public class BadgedImageView extends FrameLayout { +public class BadgedImageView extends ConstraintLayout { /** Same value as Launcher3 dot code */ public static final float WHITE_SCRIM_ALPHA = 0.54f; /** Same as value in Launcher3 IconShape */ public static final int DEFAULT_PATH_SIZE = 100; - /** Same as value in Launcher3 BaseIconFactory */ - private static final float ICON_BADGE_SCALE = 0.444f; /** * Flags that suppress the visibility of the 'new' dot, for one reason or another. If any of @@ -105,11 +104,13 @@ public class BadgedImageView extends FrameLayout { public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + // We manage positioning the badge ourselves + setLayoutDirection(LAYOUT_DIRECTION_LTR); + + LayoutInflater.from(context).inflate(R.layout.badged_image_view, this); - mBubbleIcon = new ImageView(context); - addView(mBubbleIcon); - mAppIcon = new ImageView(context); - addView(mAppIcon); + mBubbleIcon = findViewById(R.id.icon_view); + mAppIcon = findViewById(R.id.app_icon_view); final TypedArray ta = mContext.obtainStyledAttributes(attrs, new int[]{android.R.attr.src}, defStyleAttr, defStyleRes); @@ -161,6 +162,7 @@ public class BadgedImageView extends FrameLayout { public void setRenderedBubble(BubbleViewProvider bubble) { mBubble = bubble; mBubbleIcon.setImageBitmap(bubble.getBubbleIcon()); + mAppIcon.setImageBitmap(bubble.getAppBadge()); if (mDotSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK)) { hideBadge(); } else { @@ -348,26 +350,17 @@ public class BadgedImageView extends FrameLayout { } void showBadge() { - Bitmap badge = mBubble.getAppBadge(); - if (badge == null) { + if (mBubble.getAppBadge() == null) { mAppIcon.setVisibility(GONE); return; } - - final int bubbleSize = mBubble.getBubbleIcon().getWidth(); - final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize); - - FrameLayout.LayoutParams appIconParams = (LayoutParams) mAppIcon.getLayoutParams(); - appIconParams.height = badgeSize; - appIconParams.width = badgeSize; + int translationX; if (mOnLeft) { - appIconParams.gravity = Gravity.BOTTOM | Gravity.LEFT; + translationX = -(mBubbleIcon.getWidth() - mAppIcon.getWidth()); } else { - appIconParams.gravity = Gravity.BOTTOM | Gravity.RIGHT; + translationX = 0; } - mAppIcon.setLayoutParams(appIconParams); - - mAppIcon.setImageBitmap(badge); + mAppIcon.setTranslationX(translationX); mAppIcon.setVisibility(VISIBLE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index af59062ba0f3..9ae67a9bf227 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -386,13 +386,14 @@ public class BubbleExpandedView extends LinearLayout { final TypedArray ta = mContext.obtainStyledAttributes(new int[] { android.R.attr.dialogCornerRadius, android.R.attr.colorBackgroundFloating}); - mCornerRadius = ta.getDimensionPixelSize(0, 0); + boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( + mContext.getResources()); + mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; mBackgroundColorFloating = ta.getColor(1, Color.WHITE); mExpandedViewContainer.setBackgroundColor(mBackgroundColorFloating); ta.recycle(); - if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows( - mContext.getResources())) { + if (mTaskView != null) { mTaskView.setCornerRadius(mCornerRadius); } updatePointerView(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index b40021ec82a7..79b765356e9e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -70,6 +70,7 @@ import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; @@ -822,7 +823,9 @@ public class BubbleStackView extends FrameLayout mAnimatingOutSurfaceView = new SurfaceView(getContext()); mAnimatingOutSurfaceView.setUseAlpha(); mAnimatingOutSurfaceView.setZOrderOnTop(true); - mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius); + boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( + mContext.getResources()); + mAnimatingOutSurfaceView.setCornerRadius(supportsRoundedCorners ? mCornerRadius : 0); mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0)); mAnimatingOutSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index d70857a4b221..4f01dc60452e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -309,10 +309,11 @@ public class WMShellModule { PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + Optional<SplitScreenController> splitScreenOptional) { return new PipTransition(context, pipBoundsState, pipTransitionState, pipMenuController, pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer, - pipSurfaceTransactionHelper); + pipSurfaceTransactionHelper, splitScreenOptional); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 507204ce0229..c9c73fd8f191 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -277,6 +277,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, registerSettingObservers(mUserId); setupTimeoutListener(); updateSettings(); + updateDisplayLayout(mContext.getDisplayId()); mAccessibilityManager = AccessibilityManager.getInstance(context); mAccessibilityManager.addAccessibilityStateChangeListener( @@ -448,8 +449,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, onShortcutEnabledChanged(); } - private void updateDisplayLayout(int displayId) { + @VisibleForTesting + void updateDisplayLayout(int displayId) { final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId); + if (newDisplayLayout == null) { + Slog.w(TAG, "Failed to get new DisplayLayout."); + return; + } mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout); mTutorialHandler.onDisplayChanged(newDisplayLayout); mBackgroundPanelOrganizer.onDisplayChanged(newDisplayLayout); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index 1b2f4768110b..ec3ef5a27fe0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -123,9 +123,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { OneHandedBackgroundPanelOrganizer oneHandedBackgroundGradientOrganizer, ShellExecutor mainExecutor) { super(mainExecutor); - mDisplayLayout.set(displayLayout); + setDisplayLayout(displayLayout); mOneHandedSettingsUtil = oneHandedSettingsUtil; - updateDisplayBounds(); mAnimationController = animationController; final int animationDurationConfig = context.getResources().getInteger( R.integer.config_one_handed_translate_animation_duration); @@ -282,6 +281,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { @VisibleForTesting void setDisplayLayout(@NonNull DisplayLayout displayLayout) { mDisplayLayout.set(displayLayout); + updateDisplayBounds(); } @VisibleForTesting @@ -289,6 +289,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { return mDisplayAreaTokenMap; } + @VisibleForTesting void updateDisplayBounds() { mDefaultDisplayBounds.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height()); mLastVisualDisplayBounds.set(mDefaultDisplayBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 9575b0a720bc..e61617281286 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -584,9 +584,11 @@ public class PipAnimationController { setCurrentValue(bounds); if (inScaleTransition() || sourceHintRect == null) { if (isOutPipDirection) { - getSurfaceTransactionHelper().scale(tx, leash, end, bounds); + getSurfaceTransactionHelper().crop(tx, leash, end) + .scale(tx, leash, end, bounds); } else { - getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle) + getSurfaceTransactionHelper().crop(tx, leash, base) + .scale(tx, leash, base, bounds, angle) .round(tx, leash, base, bounds); } } else { @@ -622,13 +624,13 @@ public class PipAnimationController { if (rotationDelta == ROTATION_90) { degree = 90 * (1 - fraction); x = fraction * (end.left - start.left) - + start.left + start.right * (1 - fraction); + + start.left + start.width() * (1 - fraction); y = fraction * (end.top - start.top) + start.top; } else { degree = -90 * (1 - fraction); x = fraction * (end.left - start.left) + start.left; y = fraction * (end.top - start.top) - + start.top + start.bottom * (1 - fraction); + + start.top + start.height() * (1 - fraction); } } else { if (rotationDelta == ROTATION_90) { @@ -646,8 +648,10 @@ public class PipAnimationController { getSurfaceTransactionHelper() .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds, insets, degree, x, y, isOutPipDirection, - rotationDelta == ROTATION_270 /* clockwise */) - .round(tx, leash, sourceBounds, bounds); + rotationDelta == ROTATION_270 /* clockwise */); + if (shouldApplyCornerRadius()) { + getSurfaceTransactionHelper().round(tx, leash, sourceBounds, bounds); + } tx.apply(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 667d16636e6c..4c09a4e9938f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -25,6 +25,8 @@ import static android.util.RotationUtils.rotateBounds; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP; import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START; @@ -40,6 +42,10 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isRemovePipDirection; +import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; +import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; +import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -394,27 +400,54 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipUiEventLoggerLogger.log( PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); final WindowContainerTransaction wct = new WindowContainerTransaction(); + + if (ENABLE_SHELL_TRANSITIONS) { + if (requestEnterSplit && mSplitScreenOptional.isPresent()) { + mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo, + isPipTopLeft() + ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT); + mPipTransitionController.startExitTransition( + TRANSIT_EXIT_PIP_TO_SPLIT, wct, null /* destinationBounds */); + return; + } + } + final Rect destinationBounds = mPipBoundsState.getDisplayBounds(); final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit) ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_LEAVE_PIP; - final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, mPipBoundsState.getBounds()); - tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height()); - // We set to fullscreen here for now, but later it will be set to UNDEFINED for - // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit. - wct.setActivityWindowingMode(mToken, - direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && !requestEnterSplit - ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - : WINDOWING_MODE_FULLSCREEN); - wct.setBounds(mToken, destinationBounds); - wct.setBoundsChangeTransaction(mToken, tx); + + if (Transitions.ENABLE_SHELL_TRANSITIONS && direction == TRANSITION_DIRECTION_LEAVE_PIP) { + // When exit to fullscreen with Shell transition enabled, we update the Task windowing + // mode directly so that it can also trigger display rotation and visibility update in + // the same transition if there will be any. + wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); + // We can inherit the parent bounds as it is going to be fullscreen. The + // destinationBounds calculated above will be incorrect if this is with rotation. + wct.setBounds(mToken, null); + } else { + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, + mPipBoundsState.getBounds()); + tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height()); + // We set to fullscreen here for now, but later it will be set to UNDEFINED for + // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit. + wct.setActivityWindowingMode(mToken, + direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN + && !requestEnterSplit + ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + : WINDOWING_MODE_FULLSCREEN); + wct.setBounds(mToken, destinationBounds); + wct.setBoundsChangeTransaction(mToken, tx); + } + // Set the exiting state first so if there is fixed rotation later, the running animation // won't be interrupted by alpha animation for existing PiP. mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP); if (Transitions.ENABLE_SHELL_TRANSITIONS) { - mPipTransitionController.startTransition(destinationBounds, wct); + mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds); return; } mSyncTransactionQueue.queue(wct); @@ -479,7 +512,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, wct.setBounds(mToken, null); wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); wct.reorder(mToken, false); - mPipTransitionController.startTransition(null, wct); + mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct, + null /* destinationBounds */); return; } @@ -710,24 +744,18 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) { return; } + if (Transitions.ENABLE_SHELL_TRANSITIONS + && mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP) { + // With Shell transition, we do the cleanup in PipTransition after exiting PIP. + return; + } final WindowContainerToken token = info.token; Objects.requireNonNull(token, "Requires valid WindowContainerToken"); if (token.asBinder() != mToken.asBinder()) { Log.wtf(TAG, "Unrecognized token: " + token); return; } - clearWaitForFixedRotation(); - mPipTransitionState.setInSwipePipToHomeTransition(false); - mPictureInPictureParams = null; - mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED); - // Re-set the PIP bounds to none. - mPipBoundsState.setBounds(new Rect()); - mPipUiEventLoggerLogger.setTaskInfo(null); - mPipMenuController.detach(); - - if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) { - mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY); - } + onExitPipFinished(info); if (Transitions.ENABLE_SHELL_TRANSITIONS) { mPipTransitionController.forceFinishTransition(); @@ -824,6 +852,22 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, clearWaitForFixedRotation(); } + /** Called when exiting PIP tranisiton is finished to do the state cleanup. */ + void onExitPipFinished(TaskInfo info) { + clearWaitForFixedRotation(); + mPipTransitionState.setInSwipePipToHomeTransition(false); + mPictureInPictureParams = null; + mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED); + // Re-set the PIP bounds to none. + mPipBoundsState.setBounds(new Rect()); + mPipUiEventLoggerLogger.setTaskInfo(null); + mPipMenuController.detach(); + + if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) { + mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY); + } + } + private void fadeExistingPip(boolean show) { final float alphaStart = show ? 0 : 1; final float alphaEnd = show ? 1 : 0; @@ -1280,7 +1324,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct, @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) { if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { - mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct); + mSplitScreenOptional.ifPresent(splitScreenController -> + splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct)); } else { mTaskOrganizer.applyTransaction(wct); } @@ -1415,7 +1460,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** * Fades out and removes an overlay surface. */ - private void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback, + void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback, boolean withStartDelay) { if (surface == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 0fcfced2dcfc..c6794b84c42d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -19,10 +19,16 @@ package com.android.wm.shell.pip; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.util.RotationUtils.deltaRotation; +import static android.util.RotationUtils.rotateBounds; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; +import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static android.window.TransitionInfo.isIndependent; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; @@ -31,19 +37,23 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; +import static com.android.wm.shell.transition.Transitions.isOpeningType; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; -import android.util.Log; +import android.util.ArrayMap; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -51,7 +61,11 @@ import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.CounterRotator; + +import java.util.Optional; /** * Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and @@ -61,13 +75,19 @@ public class PipTransition extends PipTransitionController { private static final String TAG = PipTransition.class.getSimpleName(); + private final Context mContext; private final PipTransitionState mPipTransitionState; private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final Optional<SplitScreenController> mSplitScreenOptional; private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; - private Rect mExitDestinationBounds = new Rect(); - private IBinder mExitTransition = null; + private final Rect mExitDestinationBounds = new Rect(); + @Nullable + private IBinder mExitTransition; + /** The Task window that is currently in PIP windowing mode. */ + @Nullable + private WindowContainerToken mCurrentPipTaskToken; public PipTransition(Context context, PipBoundsState pipBoundsState, @@ -77,13 +97,16 @@ public class PipTransition extends PipTransitionController { PipAnimationController pipAnimationController, Transitions transitions, @NonNull ShellTaskOrganizer shellTaskOrganizer, - PipSurfaceTransactionHelper pipSurfaceTransactionHelper) { + PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + Optional<SplitScreenController> splitScreenOptional) { super(pipBoundsState, pipMenuController, pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer); + mContext = context; mPipTransitionState = pipTransitionState; mEnterExitAnimationDuration = context.getResources() .getInteger(R.integer.config_pipResizeAnimationDuration); mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + mSplitScreenOptional = splitScreenOptional; } @Override @@ -101,116 +124,66 @@ public class PipTransition extends PipTransitionController { } @Override - public void startTransition(Rect destinationBounds, WindowContainerTransaction out) { + public void startExitTransition(int type, WindowContainerTransaction out, + @Nullable Rect destinationBounds) { if (destinationBounds != null) { mExitDestinationBounds.set(destinationBounds); - mExitTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this); - } else { - mTransitions.startTransition(TRANSIT_REMOVE_PIP, out, this); } + mExitTransition = mTransitions.startTransition(type, out, this); } @Override - public boolean startAnimation(@android.annotation.NonNull IBinder transition, - @android.annotation.NonNull TransitionInfo info, - @android.annotation.NonNull SurfaceControl.Transaction startTransaction, - @android.annotation.NonNull SurfaceControl.Transaction finishTransaction, - @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) { - - if (mExitTransition == transition || info.getType() == TRANSIT_EXIT_PIP) { + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Exiting PIP. + final int type = info.getType(); + if (transition.equals(mExitTransition)) { + mExitDestinationBounds.setEmpty(); mExitTransition = null; - if (info.getChanges().size() == 1) { - if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(null, null); - mFinishCallback = null; - throw new RuntimeException("Previous callback not called, aborting exit PIP."); - } - final TransitionInfo.Change change = info.getChanges().get(0); - mFinishCallback = finishCallback; - startTransaction.apply(); - boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(), - new Rect(mExitDestinationBounds)); - mExitDestinationBounds.setEmpty(); - return success; - } else { - Log.e(TAG, "Got an exit-pip transition with unexpected change-list"); - } - } - - if (info.getType() == TRANSIT_REMOVE_PIP) { if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); + mFinishCallback.onTransitionFinished(null, null); mFinishCallback = null; - throw new RuntimeException("Previous callback not called, aborting remove PIP."); + throw new RuntimeException("Previous callback not called, aborting exit PIP."); } - startTransaction.apply(); - finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), - mPipBoundsState.getDisplayBounds()); - finishCallback.onTransitionFinished(null, null); - return true; - } - - // We only support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps - // that enter PiP instantly on opening, mostly from CTS/Flicker tests) - if (info.getType() != TRANSIT_PIP && info.getType() != TRANSIT_OPEN) { - // In case the PIP window is part of rotation transition, reset the bounds and rounded - // corner. - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getMode() == TRANSIT_CHANGE && change.getTaskInfo() != null - && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode() - == WINDOWING_MODE_PINNED) { - final SurfaceControl leash = change.getLeash(); - final Rect destBounds = mPipBoundsState.getBounds(); - final boolean isInPip = mPipTransitionState.isInPip(); - mSurfaceTransactionHelper - .crop(startTransaction, leash, destBounds) - .round(startTransaction, leash, isInPip); - mSurfaceTransactionHelper - .crop(finishTransaction, leash, destBounds) - .round(finishTransaction, leash, isInPip); - break; - } + final TransitionInfo.Change exitPipChange = findCurrentPipChange(info); + if (exitPipChange == null) { + throw new RuntimeException("Cannot find the pip window for exit-pip transition."); } - return false; - } - // Search for an Enter PiP transition (along with a show wallpaper one) - TransitionInfo.Change enterPip = null; - TransitionInfo.Change wallpaper = null; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getTaskInfo() != null - && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode() - == WINDOWING_MODE_PINNED) { - enterPip = change; - } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - wallpaper = change; + switch (type) { + case TRANSIT_EXIT_PIP: + startExitAnimation(info, startTransaction, finishCallback, exitPipChange); + break; + case TRANSIT_EXIT_PIP_TO_SPLIT: + startExitToSplitAnimation(info, startTransaction, finishTransaction, + finishCallback, exitPipChange); + break; + case TRANSIT_REMOVE_PIP: + removePipImmediately(info, startTransaction, finishTransaction, finishCallback, + exitPipChange); + break; + default: + throw new IllegalStateException("mExitTransition with unexpected transit type=" + + transitTypeToString(type)); } - } - if (enterPip == null) { - return false; - } - - if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); - mFinishCallback = null; - throw new RuntimeException("Previous callback not called, aborting entering PIP."); + mCurrentPipTaskToken = null; + return true; } - // Show the wallpaper if there is a wallpaper change. - if (wallpaper != null) { - startTransaction.show(wallpaper.getLeash()); - startTransaction.setAlpha(wallpaper.getLeash(), 1.f); + // Entering PIP. + if (isEnteringPip(info, mCurrentPipTaskToken)) { + return startEnterAnimation(info, startTransaction, finishTransaction, finishCallback); } - mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); - mFinishCallback = finishCallback; - return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(), - startTransaction, finishTransaction, enterPip.getStartRotation(), - enterPip.getEndRotation()); + // For transition that we don't animate, we may need to update the PIP surface, otherwise it + // will be reset after the transition. + updatePipForUnhandledTransition(info, startTransaction, finishTransaction); + return false; } @Nullable @@ -219,7 +192,6 @@ public class PipTransition extends PipTransitionController { @NonNull TransitionRequestInfo request) { if (request.getType() == TRANSIT_PIP) { WindowContainerTransaction wct = new WindowContainerTransaction(); - mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED); if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { wct.setActivityWindowingMode(request.getTriggerTask().token, WINDOWING_MODE_UNDEFINED); @@ -253,6 +225,7 @@ public class PipTransition extends PipTransitionController { new Rect(mExitDestinationBounds)); } mExitDestinationBounds.setEmpty(); + mCurrentPipTaskToken = null; } @Override @@ -285,7 +258,142 @@ public class PipTransition extends PipTransitionController { mFinishCallback = null; } - private boolean startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, + @Nullable + private TransitionInfo.Change findCurrentPipChange(@NonNull TransitionInfo info) { + if (mCurrentPipTaskToken == null) { + return null; + } + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (mCurrentPipTaskToken.equals(change.getContainer())) { + return change; + } + } + return null; + } + + private void startExitAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull TransitionInfo.Change pipChange) { + TransitionInfo.Change displayRotationChange = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getMode() == TRANSIT_CHANGE + && (change.getFlags() & FLAG_IS_DISPLAY) != 0 + && change.getStartRotation() != change.getEndRotation()) { + displayRotationChange = change; + break; + } + } + + if (displayRotationChange != null) { + // Exiting PIP to fullscreen with orientation change. + startExpandAndRotationAnimation(info, startTransaction, finishCallback, + displayRotationChange, pipChange); + return; + } + + // When there is no rotation, we can simply expand the PIP window. + mFinishCallback = (wct, wctCB) -> { + mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); + finishCallback.onTransitionFinished(wct, wctCB); + }; + + // Set the initial frame as scaling the end to the start. + final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); + final Point offset = pipChange.getEndRelOffset(); + destinationBounds.offset(-offset.x, -offset.y); + startTransaction.setWindowCrop(pipChange.getLeash(), destinationBounds); + mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(), + destinationBounds, mPipBoundsState.getBounds()); + startTransaction.apply(); + startExpandAnimation(pipChange.getTaskInfo(), pipChange.getLeash(), destinationBounds); + } + + private void startExpandAndRotationAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull TransitionInfo.Change displayRotationChange, + @NonNull TransitionInfo.Change pipChange) { + final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(), + displayRotationChange.getEndRotation()); + final int displayW = displayRotationChange.getEndAbsBounds().width(); + final int displayH = displayRotationChange.getEndAbsBounds().height(); + + // Counter-rotate all "going-away" things since they are still in the old orientation. + final ArrayMap<WindowContainerToken, CounterRotator> counterRotators = new ArrayMap<>(); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (!Transitions.isClosingType(change.getMode()) + || !isIndependent(change, info) + || change.getParent() == null) { + continue; + } + CounterRotator crot = counterRotators.get(change.getParent()); + if (crot == null) { + crot = new CounterRotator(); + crot.setup(startTransaction, + info.getChange(change.getParent()).getLeash(), + rotateDelta, displayW, displayH); + if (crot.getSurface() != null) { + // Wallpaper should be placed at the bottom. + final int layer = (change.getFlags() & FLAG_IS_WALLPAPER) == 0 + ? info.getChanges().size() - i + : -1; + startTransaction.setLayer(crot.getSurface(), layer); + } + counterRotators.put(change.getParent(), crot); + } + crot.addChild(startTransaction, change.getLeash()); + } + mFinishCallback = (wct, wctCB) -> { + for (int i = 0; i < counterRotators.size(); ++i) { + counterRotators.valueAt(i).cleanUp(info.getRootLeash()); + } + mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); + finishCallback.onTransitionFinished(wct, wctCB); + }; + + // Get the start bounds in new orientation. + final Rect startBounds = new Rect(pipChange.getStartAbsBounds()); + rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta); + final Rect endBounds = new Rect(pipChange.getEndAbsBounds()); + final Point offset = pipChange.getEndRelOffset(); + startBounds.offset(-offset.x, -offset.y); + endBounds.offset(-offset.x, -offset.y); + + // Reverse the rotation direction for expansion. + final int pipRotateDelta = deltaRotation(rotateDelta, 0); + + // Set the start frame. + final int degree, x, y; + if (pipRotateDelta == ROTATION_90) { + degree = 90; + x = startBounds.right; + y = startBounds.top; + } else { + degree = -90; + x = startBounds.left; + y = startBounds.bottom; + } + mSurfaceTransactionHelper.rotateAndScaleWithCrop(startTransaction, pipChange.getLeash(), + endBounds, startBounds, new Rect(), degree, x, y, true /* isExpanding */, + pipRotateDelta == ROTATION_270 /* clockwise */); + startTransaction.apply(); + + // Expand and rotate the pip window to fullscreen. + final PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getAnimator(pipChange.getTaskInfo(), pipChange.getLeash(), + startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP, + 0 /* startingAngle */, pipRotateDelta); + animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) + .setPipAnimationCallback(mPipAnimationCallback) + .setDuration(mEnterExitAnimationDuration) + .start(); + } + + private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, final Rect destinationBounds) { PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), @@ -296,8 +404,87 @@ public class PipTransition extends PipTransitionController { .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) .start(); + } - return true; + /** For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task. */ + private void removePipImmediately(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull TransitionInfo.Change pipChange) { + startTransaction.apply(); + finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), + mPipBoundsState.getDisplayBounds()); + mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); + finishCallback.onTransitionFinished(null, null); + } + + /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */ + private static boolean isEnteringPip(@NonNull TransitionInfo info, + @Nullable WindowContainerToken currentPipTaskToken) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED + && !change.getContainer().equals(currentPipTaskToken)) { + // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps + // that enter PiP instantly on opening, mostly from CTS/Flicker tests) + if (info.getType() == TRANSIT_PIP || info.getType() == TRANSIT_OPEN) { + return true; + } + // This can happen if the request to enter PIP happens when we are collecting for + // another transition, such as TRANSIT_CHANGE (display rotation). + if (info.getType() == TRANSIT_CHANGE) { + return true; + } + + // Please file a bug to handle the unexpected transition type. + throw new IllegalStateException("Entering PIP with unexpected transition type=" + + transitTypeToString(info.getType())); + } + } + return false; + } + + private boolean startEnterAnimation(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + // Search for an Enter PiP transition (along with a show wallpaper one) + TransitionInfo.Change enterPip = null; + TransitionInfo.Change wallpaper = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { + enterPip = change; + } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { + wallpaper = change; + } + } + if (enterPip == null) { + return false; + } + // Keep track of the PIP task. + mCurrentPipTaskToken = enterPip.getContainer(); + + if (mFinishCallback != null) { + mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */); + mFinishCallback = null; + throw new RuntimeException("Previous callback not called, aborting entering PIP."); + } + + // Show the wallpaper if there is a wallpaper change. + if (wallpaper != null) { + startTransaction.show(wallpaper.getLeash()); + startTransaction.setAlpha(wallpaper.getLeash(), 1.f); + } + + mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); + mFinishCallback = finishCallback; + return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(), + startTransaction, finishTransaction, enterPip.getStartRotation(), + enterPip.getEndRotation()); } private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash, @@ -348,6 +535,11 @@ public class PipTransition extends PipTransitionController { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); + if (sourceHintRect == null) { + // We use content overlay when there is no source rect hint to enter PiP use bounds + // animation. + animator.setUseContentOverlay(mContext); + } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { startTransaction.setAlpha(leash, 0f); // PiP menu is attached late in the process here to avoid any artifacts on the leash @@ -369,6 +561,68 @@ public class PipTransition extends PipTransitionController { return true; } + private void startExitToSplitAnimation(TransitionInfo info, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction, + Transitions.TransitionFinishCallback finishCallback, + TransitionInfo.Change pipChange) { + final int changeSize = info.getChanges().size(); + if (changeSize < 4) { + throw new RuntimeException( + "Got an exit-pip-to-split transition with unexpected change-list"); + } + for (int i = changeSize - 1; i >= 0; i--) { + final TransitionInfo.Change change = info.getChanges().get(i); + final int mode = change.getMode(); + + if (mode == TRANSIT_CHANGE && change.getParent() != null) { + // TODO: perform resize/expand animation for reparented child task. + continue; + } + + if (isOpeningType(mode) && change.getParent() == null) { + final SurfaceControl leash = change.getLeash(); + final Rect endBounds = change.getEndAbsBounds(); + startTransaction + .show(leash) + .setAlpha(leash, 1f) + .setPosition(leash, endBounds.left, endBounds.top) + .setWindowCrop(leash, endBounds.width(), endBounds.height()); + } + } + mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction); + startTransaction.apply(); + + mPipOrganizer.onExitPipFinished(pipChange.getTaskInfo()); + finishCallback.onTransitionFinished(null, null); + } + + private void updatePipForUnhandledTransition(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + if (mCurrentPipTaskToken == null) { + return; + } + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (!mCurrentPipTaskToken.equals(change.getContainer())) { + continue; + } + // When the PIP window is visible and being a part of the transition, such as display + // rotation, we need to update its bounds and rounded corner. + final SurfaceControl leash = change.getLeash(); + final Rect destBounds = mPipBoundsState.getBounds(); + final boolean isInPip = mPipTransitionState.isInPip(); + mSurfaceTransactionHelper + .crop(startTransaction, leash, destBounds) + .round(startTransaction, leash, isInPip); + mSurfaceTransactionHelper + .crop(finishTransaction, leash, destBounds) + .round(finishTransaction, leash, isInPip); + break; + } + } + private void finishResizeForMenu(Rect destinationBounds) { mPipMenuController.movePipMenu(null, null, destinationBounds); mPipMenuController.updateMenuBounds(destinationBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 376f3298a83c..22b3ef3bfe0b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -19,7 +19,9 @@ package com.android.wm.shell.pip; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; +import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; +import android.annotation.Nullable; import android.app.PictureInPictureParams; import android.app.TaskInfo; import android.content.ComponentName; @@ -68,6 +70,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { return; } + if (isInPipDirection(direction) && animator.getContentOverlay() != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlay(), + animator::clearContentOverlay, true /* withStartDelay*/); + } onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx); sendOnPipTransitionFinished(direction); } @@ -75,6 +81,11 @@ public abstract class PipTransitionController implements Transitions.TransitionH @Override public void onPipAnimationCancel(TaskInfo taskInfo, PipAnimationController.PipTransitionAnimator animator) { + final int direction = animator.getTransitionDirection(); + if (isInPipDirection(direction) && animator.getContentOverlay() != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlay(), + animator::clearContentOverlay, true /* withStartDelay */); + } sendOnPipTransitionCancelled(animator.getTransitionDirection()); } }; @@ -98,9 +109,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH } /** - * Called when the Shell wants to starts a transition/animation. + * Called when the Shell wants to start an exit Pip transition/animation. */ - public void startTransition(Rect destinationBounds, WindowContainerTransaction out) { + public void startExitTransition(int type, WindowContainerTransaction out, + @Nullable Rect destinationBounds) { // Default implementation does nothing. } 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 3de59b48bcf0..7decb54d16bb 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 @@ -292,6 +292,7 @@ public class RecentTasksController implements TaskStackListenerCallback, * Invalidates this instance, preventing future calls from updating the controller. */ void invalidate() { + Slog.d("b/206648922", "invalidating controller: " + mController); mController = null; } @@ -317,7 +318,8 @@ public class RecentTasksController implements TaskStackListenerCallback, (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) .toArray(new GroupedRecentTaskInfo[0]), true /* blocking */); - Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0]); + Slog.d("b/206648922", "getRecentTasks(" + maxNum + "): " + out[0] + + " mController=" + mController); return out[0]; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 1ba1d22e7831..122fc9f5f780 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -19,7 +19,6 @@ package com.android.wm.shell.splitscreen; import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; -import android.graphics.Rect; import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -45,11 +44,6 @@ class SideStage extends StageTaskListener { stageTaskUnfoldController); } - void moveToTop(Rect rootBounds, WindowContainerTransaction wct) { - final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */); - } - boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { // No matter if the root task is empty or not, moving the root to bottom because it no // longer preserves visible child task. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 1f49a4cc3ab9..6921448ba9ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -26,6 +26,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -238,6 +239,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction()); } + public void prepareEnterSplitScreen(WindowContainerTransaction wct, + ActivityManager.RunningTaskInfo taskInfo, int startPosition) { + mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition); + } + + public void finishEnterSplitScreen(SurfaceControl.Transaction t) { + mStageCoordinator.finishEnterSplitScreen(t); + } + public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) { final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE; final int stagePosition = @@ -310,7 +320,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { - if (!Transitions.ENABLE_SHELL_TRANSITIONS) { + if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } @@ -366,7 +376,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) { - if (!isSplitScreenVisible()) return null; + if (ENABLE_SHELL_TRANSITIONS || apps.length < 2) return null; + // TODO(b/206487881): Integrate this with shell transition. final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) .setContainerLayer() .setName("RecentsAnimationSplitTasks") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 54d8ece26c1a..0aa8d7e2f1a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -28,7 +28,8 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; -import static com.android.wm.shell.transition.Transitions.isOpeningType; +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -148,7 +149,7 @@ class SplitScreenTransitions { t.setWindowCrop(leash, change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); } - boolean isOpening = isOpeningType(info.getType()); + boolean isOpening = isOpeningTransition(info); if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { // fade in startExampleAnimation(leash, true /* show */); @@ -305,6 +306,12 @@ class SplitScreenTransitions { mTransitions.getAnimExecutor().execute(va::start); } + private boolean isOpeningTransition(TransitionInfo info) { + return Transitions.isOpeningType(info.getType()) + || info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE + || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN; + } + /** Bundled information of dismiss transition. */ static class DismissTransition { IBinder mTransition; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index af0ac5553c21..83830ec56c32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -46,6 +46,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; import static com.android.wm.shell.transition.Transitions.isClosingType; import static com.android.wm.shell.transition.Transitions.isOpeningType; @@ -323,7 +324,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!evictWct.isEmpty()) { wct.merge(evictWct, true /* transfer */); } - mTaskOrganizer.applyTransaction(wct); + + if (ENABLE_SHELL_TRANSITIONS) { + prepareEnterSplitScreen(wct); + mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, + wct, null, this); + } else { + mTaskOrganizer.applyTransaction(wct); + } return true; } @@ -372,7 +380,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { + // Init divider first to make divider leash for remote animation target. + mSplitLayout.init(); + // Set false to avoid record new bounds with old task still on top; + mShouldUpdateRecents = false; final WindowContainerTransaction wct = new WindowContainerTransaction(); + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct); + prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct); // Need to add another wrapper here in shell so that we can inject the divider bar // and also manage the process elevation via setRunningRemote IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { @@ -388,6 +403,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, augmentedNonApps[i] = nonApps[i]; } augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget(); + + IRemoteAnimationFinishedCallback wrapCallback = + new IRemoteAnimationFinishedCallback.Stub() { + @Override + public void onAnimationFinished() throws RemoteException { + mShouldUpdateRecents = true; + mSyncQueue.queue(evictWct); + mSyncQueue.runInSync(t -> setDividerVisibility(true, t)); + finishedCallback.onAnimationFinished(); + } + }; try { try { ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( @@ -396,8 +422,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, Slog.e(TAG, "Unable to boost animation thread. This should only happen" + " during unit tests"); } - adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps, - finishedCallback); + adapter.getRunner().onAnimationStart(transit, apps, wallpapers, + augmentedNonApps, wrapCallback); } catch (RemoteException e) { Slog.e(TAG, "Error starting remote animation", e); } @@ -405,6 +431,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onAnimationCancelled() { + mShouldUpdateRecents = true; + mSyncQueue.queue(evictWct); + mSyncQueue.runInSync(t -> setDividerVisibility(true, t)); try { adapter.getRunner().onAnimationCancelled(); } catch (RemoteException e) { @@ -427,10 +456,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(sidePosition, wct); mSplitLayout.setDivideRatio(splitRatio); - // Build a request WCT that will launch both apps such that task 0 is on the main stage - // while task 1 is on the side stage. - mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); - mSideStage.setBounds(getSideStageBounds(), wct); + if (mMainStage.isActive()) { + mMainStage.moveToTop(getMainStageBounds(), wct); + } else { + // Build a request WCT that will launch both apps such that task 0 is on the main stage + // while task 1 is on the side stage. + mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); + } + mSideStage.moveToTop(getSideStageBounds(), wct); // Make sure the launch options will put tasks in the corresponding split roots addActivityOptions(mainOptions, mMainStage); @@ -699,12 +732,47 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, * an existing WindowContainerTransaction (rather than applying immediately). This is intended * to be used when exiting split might be bundled with other window operations. */ - void prepareExitSplitScreen(@StageType int stageToTop, + private void prepareExitSplitScreen(@StageType int stageToTop, @NonNull WindowContainerTransaction wct) { + if (!mMainStage.isActive()) return; mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); } + private void prepareEnterSplitScreen(WindowContainerTransaction wct) { + prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED); + } + + /** + * Prepare transaction to active split screen. If there's a task indicated, the task will be put + * into side stage. + */ + void prepareEnterSplitScreen(WindowContainerTransaction wct, + @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) { + if (mMainStage.isActive()) return; + + if (taskInfo != null) { + setSideStagePosition(startPosition, wct); + mSideStage.addTask(taskInfo, wct); + } + mMainStage.activate(getMainStageBounds(), wct, true /* includingTopTask */); + mSideStage.moveToTop(getSideStageBounds(), wct); + } + + void finishEnterSplitScreen(SurfaceControl.Transaction t) { + mSplitLayout.init(); + setDividerVisibility(true, t); + setSplitsVisible(true); + mShouldUpdateRecents = true; + updateRecentTasksSplitPair(); + if (!mLogger.hasStartedSession()) { + mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), + getMainStagePosition(), mMainStage.getTopChildTaskUid(), + getSideStagePosition(), mSideStage.getTopChildTaskUid(), + mSplitLayout.isLandscape()); + } + } + void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { outTopOrLeftBounds.set(mSplitLayout.getBounds1()); outBottomOrRightBounds.set(mSplitLayout.getBounds2()); @@ -770,7 +838,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLandscape()); } - updateRecentTasksSplitPair(); + if (present && visible) { + updateRecentTasksSplitPair(); + } for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onTaskStageChanged(taskId, stage, visible); @@ -786,7 +856,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mShouldUpdateRecents) { return; } - mRecentTasks.ifPresent(recentTasks -> { Rect topLeftBounds = mSplitLayout.getBounds1(); Rect bottomRightBounds = mSplitLayout.getBounds2(); @@ -883,12 +952,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void applyDividerVisibility(SurfaceControl.Transaction t) { final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); - if (dividerLeash == null) { - return; - } + if (dividerLeash == null) return; if (mDividerVisible) { t.show(dividerLeash); + t.setAlpha(dividerLeash, 1); t.setLayer(dividerLeash, SPLIT_DIVIDER_LAYER); t.setPosition(dividerLeash, mSplitLayout.getDividerBounds().left, mSplitLayout.getDividerBounds().top); @@ -910,11 +978,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } else if (isSideStage) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSplitLayout.init(); // Make sure the main stage is active. - mMainStage.activate(getMainStageBounds(), wct, true /* reparent */); - mSideStage.moveToTop(getSideStageBounds(), wct); + prepareEnterSplitScreen(wct); mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t)); + mSyncQueue.runInSync(t -> { + updateSurfaceBounds(mSplitLayout, t); + setDividerVisibility(true, t); + }); } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; @@ -1213,8 +1284,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); - mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */); - mSideStage.moveToTop(getSideStageBounds(), out); + prepareEnterSplitScreen(out); mSplitTransitions.mPendingEnter = transition; } } @@ -1226,18 +1296,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Once the pending enter transition got merged, make sure to bring divider bar visible and // clear the pending transition from cache to prevent mess-up the following state. if (transition == mSplitTransitions.mPendingEnter) { - mSplitLayout.init(); - setDividerVisibility(true, null /* transaction */); - setSplitsVisible(true); - mShouldUpdateRecents = true; - updateRecentTasksSplitPair(); - - if (!mLogger.hasStartedSession()) { - mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), - getMainStagePosition(), mMainStage.getTopChildTaskUid(), - getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLandscape()); - } + finishEnterSplitScreen(null); mSplitTransitions.mPendingEnter = null; } } @@ -1325,19 +1384,20 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, sideChild = change; } } - if (mainChild == null || sideChild == null) { - throw new IllegalStateException("Launched 2 tasks in split, but didn't receive" - + " 2 tasks in transition. Possibly one of them failed to launch"); - // TODO: fallback logic. Probably start a new transition to exit split before - // applying anything here. Ideally consolidate with transition-merging. - } - - // Update local states (before animating). - mSplitLayout.init(); - setDividerVisibility(true, t); - setSplitsVisible(true); - addDividerBarToTransition(info, t, true /* show */); + // TODO: fallback logic. Probably start a new transition to exit split before applying + // anything here. Ideally consolidate with transition-merging. + if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { + if (mainChild == null && sideChild == null) { + throw new IllegalStateException("Launched a task in split, but didn't receive any" + + " task in transition."); + } + } else { + if (mainChild == null || sideChild == null) { + throw new IllegalStateException("Launched 2 tasks in split, but didn't receive" + + " 2 tasks in transition. Possibly one of them failed to launch"); + } + } // Make some noise if things aren't totally expected. These states shouldn't effect // transitions locally, but remotes (like Launcher) may get confused if they were @@ -1345,27 +1405,19 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // aren't serialized with transition callbacks. // TODO(b/184679596): Find a way to either include task-org information in // the transition, or synchronize task-org callbacks. - if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) { + if (mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId)) { Log.w(TAG, "Expected onTaskAppeared on " + mMainStage + " to have been called with " + mainChild.getTaskInfo().taskId + " before startAnimation()."); } - if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) { + if (sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId)) { Log.w(TAG, "Expected onTaskAppeared on " + mSideStage + " to have been called with " + sideChild.getTaskInfo().taskId + " before startAnimation()."); } - mShouldUpdateRecents = true; - updateRecentTasksSplitPair(); - - if (!mLogger.hasStartedSession()) { - mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), - getMainStagePosition(), mMainStage.getTopChildTaskUid(), - getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLandscape()); - } - + finishEnterSplitScreen(t); + addDividerBarToTransition(info, t, true /* show */); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 2c853c1c474c..83534c178469 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.util.SparseArray; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -301,9 +302,19 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { + // Clear overridden bounds and windowing mode to make sure the child task can inherit + // windowing mode and bounds from split root. + wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) + .setBounds(task.token, null); + wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/); } + void moveToTop(Rect rootBounds, WindowContainerTransaction wct) { + final WindowContainerToken rootToken = mRootTaskInfo.token; + wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */); + } + void setBounds(Rect bounds, WindowContainerTransaction wct) { wct.setBounds(mRootTaskInfo.token, bounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java index 0683a25ecd41..59eecb5db136 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java @@ -105,6 +105,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange * @param leash surface leash for the appeared task */ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + // Only handle child task surface here. + if (!taskInfo.hasParentTask()) return; + AnimationContext context = new AnimationContext(leash); mAnimationContextByTaskId.put(taskInfo.taskId, context); } @@ -114,6 +117,8 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange * @param taskInfo info for the vanished task */ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + if (!taskInfo.hasParentTask()) return; + AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId); if (context != null) { final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index b8cbfd9680fd..33a98b2fd80e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -74,23 +74,25 @@ public class Transitions implements RemoteCallable<Transitions> { public static final boolean ENABLE_SHELL_TRANSITIONS = SystemProperties.getBoolean("persist.debug.shell_transit", false); - /** Transition type for dismissing split-screen via dragging the divider off the screen. */ - public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 1; - - /** Transition type for launching 2 tasks simultaneously. */ - public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 2; - /** Transition type for exiting PIP via the Shell, via pressing the expand button. */ - public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 3; + public static final int TRANSIT_EXIT_PIP = TRANSIT_FIRST_CUSTOM + 1; + + public static final int TRANSIT_EXIT_PIP_TO_SPLIT = TRANSIT_FIRST_CUSTOM + 2; /** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */ - public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4; + public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 3; + + /** Transition type for launching 2 tasks simultaneously. */ + public static final int TRANSIT_SPLIT_SCREEN_PAIR_OPEN = TRANSIT_FIRST_CUSTOM + 4; /** Transition type for entering split by opening an app into side-stage. */ public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5; + /** Transition type for dismissing split-screen via dragging the divider off the screen. */ + public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 6; + /** Transition type for dismissing split-screen. */ - public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 6; + public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 7; private final WindowOrganizer mOrganizer; private final Context mContext; @@ -357,6 +359,28 @@ public class Transitions implements RemoteCallable<Transitions> { return; } + // apply transfer starting window directly if there is no other task change. + final int changeSize = info.getChanges().size(); + if (changeSize == 2) { + boolean nonTaskChange = true; + boolean transferStartingWindow = false; + for (int i = changeSize - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getTaskInfo() != null) { + nonTaskChange = false; + break; + } + if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { + transferStartingWindow = true; + } + } + if (nonTaskChange && transferStartingWindow) { + t.apply(); + onFinish(transitionToken, null /* wct */, null /* wctCB */); + return; + } + } + final ActiveTransition active = mActiveTransitions.get(activeIdx); active.mInfo = info; active.mStartT = t; diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index c4be785cff19..68b0b4e48252 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -17,11 +17,11 @@ @file:JvmName("CommonAssertions") package com.android.wm.shell.flicker -import android.graphics.Region import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.region.Region fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() { assertLayersEnd { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt index 623055f659b9..efae20731bef 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt @@ -17,23 +17,23 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.graphics.Region import com.android.server.wm.flicker.Flicker import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.region.Region class AppPairsHelper( instrumentation: Instrumentation, activityLabel: String, component: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, component) { - fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region { + fun getPrimaryBounds(dividerBounds: Region): Region { val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right, dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset) return primaryAppBounds } - fun getSecondaryBounds(dividerBounds: Region): android.graphics.Region { + fun getSecondaryBounds(dividerBounds: Region): Region { val displayBounds = WindowUtils.displayBounds val secondaryAppBounds = Region(0, dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index 2357b0debb33..8e6fa5f1314c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.graphics.Rect import android.media.session.MediaController import android.media.session.MediaSessionManager import android.os.SystemClock @@ -26,6 +25,7 @@ import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.FIND_TIMEOUT import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE +import com.android.server.wm.traces.common.Rect import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index 0448ec8f2135..7d7add48c898 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.graphics.Region import android.util.Rational import android.view.Surface import androidx.test.filters.FlakyTest @@ -41,6 +40,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.region.Region import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.helpers.SimpleAppHelper diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 0cbfc9215103..d3bb0082be07 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -128,8 +128,8 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test fun pipWindowRemainInsideVisibleBounds() { - testSpec.assertWm { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertWmVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } @@ -140,8 +140,8 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test fun pipLayerRemainInsideVisibleBounds() { - testSpec.assertLayers { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertLayersVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index 3e7e2f542a4c..f8a3aff98276 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -45,8 +45,8 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans @Presubmit @Test open fun pipAppWindowRemainInsideVisibleBounds() { - testSpec.assertWm { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertWmVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } @@ -57,8 +57,8 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans @Presubmit @Test open fun pipAppLayerRemainInsideVisibleBounds() { - testSpec.assertLayers { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertLayersVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index cba677b6f1d1..52177c2c7684 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -78,8 +78,8 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition @Presubmit @Test fun pipWindowRemainInsideVisibleBounds() { - testSpec.assertWm { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertWmVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } @@ -90,8 +90,8 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition @Presubmit @Test fun pipLayerRemainInsideVisibleBounds() { - testSpec.assertLayers { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertLayersVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index 7ed0c4934686..f9e180e8ec61 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -25,8 +25,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.traces.RegionSubject import org.junit.Assume.assumeFalse +import com.android.server.wm.flicker.traces.region.RegionSubject import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index 6e0324c17272..0499e7de9a0a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.traces.RegionSubject +import com.android.server.wm.flicker.traces.region.RegionSubject import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.Test @@ -66,8 +66,8 @@ abstract class MovePipShelfHeightTransition( @Presubmit @Test open fun pipWindowRemainInsideVisibleBounds() { - testSpec.assertWm { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertWmVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } @@ -78,8 +78,8 @@ abstract class MovePipShelfHeightTransition( @Presubmit @Test open fun pipLayerRemainInsideVisibleBounds() { - testSpec.assertLayers { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertLayersVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index 56c09498f2ba..b7bfa1b2df88 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -24,8 +24,8 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.traces.region.RegionSubject import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.traces.RegionSubject import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 62e230f8c439..c36dfda70c51 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -88,9 +88,9 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Presubmit @Test fun pipInVisibleBounds() { - testSpec.assertWm { + testSpec.assertWmVisibleRegion(pipApp.component) { val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) - coversAtMost(displayBounds, pipApp.component) + coversAtMost(displayBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index e3f544ab0fab..df58194fb91a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -101,8 +101,8 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @FlakyTest(bugId = 161435597) @Test fun pipWindowInsideDisplayBounds() { - testSpec.assertWm { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertWmVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } @@ -119,8 +119,8 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @FlakyTest(bugId = 161435597) @Test fun pipLayerInsideDisplayBounds() { - testSpec.assertLayers { - coversAtMost(displayBounds, pipApp.component) + testSpec.assertLayersVisibleRegion(pipApp.component) { + coversAtMost(displayBounds) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 0a3a84923053..16bc50750e1d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -109,6 +109,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { mSpiedTransitionState = spy(new OneHandedState()); when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay); + when(mMockDisplayController.getDisplayLayout(anyInt())).thenReturn(null); when(mMockDisplayAreaOrganizer.getDisplayAreaTokenMap()).thenReturn(new ArrayMap<>()); when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); when(mMockBackgroundOrganizer.isRegistered()).thenReturn(true); @@ -153,6 +154,13 @@ public class OneHandedControllerTest extends OneHandedTestCase { } @Test + public void testNullDisplayLayout() { + mSpiedOneHandedController.updateDisplayLayout(0); + + verify(mMockDisplayAreaOrganizer, never()).setDisplayLayout(any()); + } + + @Test public void testStartOneHandedShouldTriggerScheduleOffset() { mSpiedTransitionState.setState(STATE_NONE); mSpiedOneHandedController.setOneHandedEnabled(true); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index ef16fd391235..1d92a48c25fd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -432,4 +432,11 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse(); } + + @Test + public void testDisplayArea_setDisplayLayout_should_updateDisplayBounds() { + mSpiedDisplayAreaOrganizer.setDisplayLayout(mDisplayLayout); + + verify(mSpiedDisplayAreaOrganizer).updateDisplayBounds(); + } } diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp index a48d7f734e29..213f35a81b88 100644 --- a/libs/hwui/jni/RenderEffect.cpp +++ b/libs/hwui/jni/RenderEffect.cpp @@ -127,6 +127,32 @@ static jlong createShaderEffect( return reinterpret_cast<jlong>(shaderFilter.release()); } +static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args); + va_end(args); + return ret; +} + +static jlong createRuntimeShaderEffect(JNIEnv* env, jobject, jlong shaderBuilderHandle, + jstring inputShaderName) { + SkRuntimeShaderBuilder* builder = + reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilderHandle); + ScopedUtfChars name(env, inputShaderName); + + if (builder->child(name.c_str()).fChild == nullptr) { + ThrowIAEFmt(env, + "unable to find a uniform with the name '%s' of the correct " + "type defined by the provided RuntimeShader", + name.c_str()); + return 0; + } + + sk_sp<SkImageFilter> filter = SkImageFilters::RuntimeShader(*builder, name.c_str(), nullptr); + return reinterpret_cast<jlong>(filter.release()); +} + static void RenderEffect_safeUnref(SkImageFilter* filter) { SkSafeUnref(filter); } @@ -136,15 +162,16 @@ static jlong getRenderEffectFinalizer(JNIEnv*, jobject) { } static const JNINativeMethod gRenderEffectMethods[] = { - {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer}, - {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect}, - {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect}, - {"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect}, - {"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect}, - {"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect}, - {"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect}, - {"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect} -}; + {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer}, + {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect}, + {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect}, + {"nativeCreateBitmapEffect", "(JFFFFFFFF)J", (void*)createBitmapEffect}, + {"nativeCreateColorFilterEffect", "(JJ)J", (void*)createColorFilterEffect}, + {"nativeCreateBlendModeEffect", "(JJI)J", (void*)createBlendModeEffect}, + {"nativeCreateChainEffect", "(JJ)J", (void*)createChainEffect}, + {"nativeCreateShaderEffect", "(J)J", (void*)createShaderEffect}, + {"nativeCreateRuntimeShaderEffect", "(JLjava/lang/String;)J", + (void*)createRuntimeShaderEffect}}; int register_android_graphics_RenderEffect(JNIEnv* env) { android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect", diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java index f509252e7542..71cb0e32a706 100644 --- a/location/java/android/location/GnssMeasurementRequest.java +++ b/location/java/android/location/GnssMeasurementRequest.java @@ -16,10 +16,14 @@ package android.location; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.util.TimeUtils; + +import com.android.internal.util.Preconditions; import java.util.Objects; @@ -29,13 +33,16 @@ import java.util.Objects; public final class GnssMeasurementRequest implements Parcelable { private final boolean mCorrelationVectorOutputsEnabled; private final boolean mFullTracking; + private final int mIntervalMillis; /** * Creates a {@link GnssMeasurementRequest} with a full list of parameters. */ - private GnssMeasurementRequest(boolean fullTracking, boolean correlationVectorOutputsEnabled) { + private GnssMeasurementRequest(boolean fullTracking, boolean correlationVectorOutputsEnabled, + int intervalMillis) { mFullTracking = fullTracking; mCorrelationVectorOutputsEnabled = correlationVectorOutputsEnabled; + mIntervalMillis = intervalMillis; } /** @@ -68,13 +75,26 @@ public final class GnssMeasurementRequest implements Parcelable { return mFullTracking; } + /** + * Represents the requested time interval between the reported measurements in milliseconds. + * + * <p>If the time interval is not set, the default value is 0, which means the fastest rate the + * GNSS chipset can report. + * + * <p>The GNSS chipset may report measurements with a rate faster than requested. + */ + public @IntRange(from = 0) int getIntervalMillis() { + return mIntervalMillis; + } + @NonNull public static final Creator<GnssMeasurementRequest> CREATOR = new Creator<GnssMeasurementRequest>() { @Override @NonNull public GnssMeasurementRequest createFromParcel(@NonNull Parcel parcel) { - return new GnssMeasurementRequest(parcel.readBoolean(), parcel.readBoolean()); + return new GnssMeasurementRequest(parcel.readBoolean(), parcel.readBoolean(), + parcel.readInt()); } @Override @@ -87,6 +107,7 @@ public final class GnssMeasurementRequest implements Parcelable { public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeBoolean(mFullTracking); parcel.writeBoolean(mCorrelationVectorOutputsEnabled); + parcel.writeInt(mIntervalMillis); } @NonNull @@ -94,11 +115,13 @@ public final class GnssMeasurementRequest implements Parcelable { public String toString() { StringBuilder s = new StringBuilder(); s.append("GnssMeasurementRequest["); + s.append("@"); + TimeUtils.formatDuration(mIntervalMillis, s); if (mFullTracking) { - s.append("FullTracking"); + s.append(", FullTracking"); } if (mCorrelationVectorOutputsEnabled) { - s.append(", CorrelationVectorOutPuts"); + s.append(", CorrelationVectorOutputs"); } s.append(']'); return s.toString(); @@ -115,12 +138,15 @@ public final class GnssMeasurementRequest implements Parcelable { if (mCorrelationVectorOutputsEnabled != other.mCorrelationVectorOutputsEnabled) { return false; } + if (mIntervalMillis != other.mIntervalMillis) { + return false; + } return true; } @Override public int hashCode() { - return Objects.hash(mFullTracking, mCorrelationVectorOutputsEnabled); + return Objects.hash(mFullTracking, mCorrelationVectorOutputsEnabled, mIntervalMillis); } @Override @@ -132,6 +158,7 @@ public final class GnssMeasurementRequest implements Parcelable { public static final class Builder { private boolean mCorrelationVectorOutputsEnabled; private boolean mFullTracking; + private int mIntervalMillis; /** * Constructs a {@link Builder} instance. @@ -145,6 +172,7 @@ public final class GnssMeasurementRequest implements Parcelable { public Builder(@NonNull GnssMeasurementRequest request) { mCorrelationVectorOutputsEnabled = request.isCorrelationVectorOutputsEnabled(); mFullTracking = request.isFullTracking(); + mIntervalMillis = request.getIntervalMillis(); } /** @@ -183,10 +211,25 @@ public final class GnssMeasurementRequest implements Parcelable { return this; } + /** + * Set the time interval between the reported measurements in milliseconds, which is 0 by + * default. + * + * <p>An interval of 0 milliseconds means the fastest rate the chipset can report. + * + * <p>The GNSS chipset may report measurements with a rate faster than requested. + */ + @NonNull public Builder setIntervalMillis(@IntRange(from = 0) int value) { + mIntervalMillis = Preconditions.checkArgumentInRange(value, 0, Integer.MAX_VALUE, + "intervalMillis"); + return this; + } + /** Builds a {@link GnssMeasurementRequest} instance as specified by this builder. */ @NonNull public GnssMeasurementRequest build() { - return new GnssMeasurementRequest(mFullTracking, mCorrelationVectorOutputsEnabled); + return new GnssMeasurementRequest(mFullTracking, mCorrelationVectorOutputsEnabled, + mIntervalMillis); } } } diff --git a/location/java/android/location/GnssSingleSatCorrection.java b/location/java/android/location/GnssSingleSatCorrection.java index aeca562fced2..262630b79cb0 100644 --- a/location/java/android/location/GnssSingleSatCorrection.java +++ b/location/java/android/location/GnssSingleSatCorrection.java @@ -26,6 +26,8 @@ import android.os.Parcelable; import com.android.internal.util.Preconditions; +import java.util.Objects; + /** * A container with measurement corrections for a single visible satellite * @@ -119,15 +121,17 @@ public final class GnssSingleSatCorrection implements Parcelable { @Nullable private final GnssReflectingPlane mReflectingPlane; - private GnssSingleSatCorrection(Builder builder) { - mSingleSatCorrectionFlags = builder.mSingleSatCorrectionFlags; - mSatId = builder.mSatId; - mConstellationType = builder.mConstellationType; - mCarrierFrequencyHz = builder.mCarrierFrequencyHz; - mProbSatIsLos = builder.mProbSatIsLos; - mExcessPathLengthMeters = builder.mExcessPathLengthMeters; - mExcessPathLengthUncertaintyMeters = builder.mExcessPathLengthUncertaintyMeters; - mReflectingPlane = builder.mReflectingPlane; + private GnssSingleSatCorrection(int singleSatCorrectionFlags, int constellationType, int satId, + float carrierFrequencyHz, float probSatIsLos, float excessPathLengthMeters, + float excessPathLengthUncertaintyMeters, GnssReflectingPlane reflectingPlane) { + mSingleSatCorrectionFlags = singleSatCorrectionFlags; + mConstellationType = constellationType; + mSatId = satId; + mCarrierFrequencyHz = carrierFrequencyHz; + mProbSatIsLos = probSatIsLos; + mExcessPathLengthMeters = excessPathLengthMeters; + mExcessPathLengthUncertaintyMeters = excessPathLengthUncertaintyMeters; + mReflectingPlane = reflectingPlane; } /** @@ -239,27 +243,49 @@ public final class GnssSingleSatCorrection implements Parcelable { return 0; } + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mSingleSatCorrectionFlags); + parcel.writeInt(mConstellationType); + parcel.writeInt(mSatId); + parcel.writeFloat(mCarrierFrequencyHz); + if (hasValidSatelliteLineOfSight()) { + parcel.writeFloat(mProbSatIsLos); + } + if (hasExcessPathLength()) { + parcel.writeFloat(mExcessPathLengthMeters); + } + if (hasExcessPathLengthUncertainty()) { + parcel.writeFloat(mExcessPathLengthUncertaintyMeters); + } + if (hasReflectingPlane()) { + mReflectingPlane.writeToParcel(parcel, flags); + } + } + public static final Creator<GnssSingleSatCorrection> CREATOR = new Creator<GnssSingleSatCorrection>() { @Override @NonNull public GnssSingleSatCorrection createFromParcel(@NonNull Parcel parcel) { - int mSingleSatCorrectionFlags = parcel.readInt(); - boolean hasReflectingPlane = - (mSingleSatCorrectionFlags & HAS_REFLECTING_PLANE_MASK) != 0; - final GnssSingleSatCorrection.Builder singleSatCorrectionBuilder = - new Builder() - .setConstellationType(parcel.readInt()) - .setSatelliteId(parcel.readInt()) - .setCarrierFrequencyHz(parcel.readFloat()) - .setProbabilityLineOfSight(parcel.readFloat()) - .setExcessPathLengthMeters(parcel.readFloat()) - .setExcessPathLengthUncertaintyMeters(parcel.readFloat()); - if (hasReflectingPlane) { - singleSatCorrectionBuilder.setReflectingPlane( - GnssReflectingPlane.CREATOR.createFromParcel(parcel)); - } - return singleSatCorrectionBuilder.build(); + int singleSatCorrectionFlags = parcel.readInt(); + int constellationType = parcel.readInt(); + int satId = parcel.readInt(); + float carrierFrequencyHz = parcel.readFloat(); + float probSatIsLos = (singleSatCorrectionFlags & HAS_PROB_SAT_IS_LOS_MASK) != 0 + ? parcel.readFloat() : 0; + float excessPathLengthMeters = + (singleSatCorrectionFlags & HAS_EXCESS_PATH_LENGTH_MASK) != 0 + ? parcel.readFloat() : 0; + float excessPathLengthUncertaintyMeters = + (singleSatCorrectionFlags & HAS_EXCESS_PATH_LENGTH_UNC_MASK) != 0 + ? parcel.readFloat() : 0; + GnssReflectingPlane reflectingPlane = + (singleSatCorrectionFlags & HAS_REFLECTING_PLANE_MASK) != 0 + ? GnssReflectingPlane.CREATOR.createFromParcel(parcel) : null; + return new GnssSingleSatCorrection(singleSatCorrectionFlags, constellationType, + satId, carrierFrequencyHz, probSatIsLos, excessPathLengthMeters, + excessPathLengthUncertaintyMeters, reflectingPlane); } @Override @@ -268,41 +294,94 @@ public final class GnssSingleSatCorrection implements Parcelable { } }; - @NonNull @Override - public String toString() { - final String format = " %-29s = %s\n"; - StringBuilder builder = new StringBuilder("GnssSingleSatCorrection:\n"); - builder.append( - String.format(format, "SingleSatCorrectionFlags = ", mSingleSatCorrectionFlags)); - builder.append(String.format(format, "ConstellationType = ", mConstellationType)); - builder.append(String.format(format, "SatId = ", mSatId)); - builder.append(String.format(format, "CarrierFrequencyHz = ", mCarrierFrequencyHz)); - builder.append(String.format(format, "ProbSatIsLos = ", mProbSatIsLos)); - builder.append(String.format(format, "ExcessPathLengthMeters = ", mExcessPathLengthMeters)); - builder.append( - String.format( - format, - "ExcessPathLengthUncertaintyMeters = ", - mExcessPathLengthUncertaintyMeters)); - if (hasReflectingPlane()) { - builder.append(String.format(format, "ReflectingPlane = ", mReflectingPlane)); + public boolean equals(Object obj) { + if (this == obj) { + return true; } - return builder.toString(); + if (!(obj instanceof GnssSingleSatCorrection)) { + return false; + } + + GnssSingleSatCorrection other = (GnssSingleSatCorrection) obj; + if (mConstellationType != other.mConstellationType) { + return false; + } + if (mSatId != other.mSatId) { + return false; + } + if (Float.compare(mCarrierFrequencyHz, other.mCarrierFrequencyHz) != 0) { + return false; + } + + if (hasValidSatelliteLineOfSight() != other.hasValidSatelliteLineOfSight()) { + return false; + } + if (hasValidSatelliteLineOfSight() + && Float.compare(mProbSatIsLos, other.mProbSatIsLos) != 0) { + return false; + } + + if (hasExcessPathLength() != other.hasExcessPathLength()) { + return false; + } + if (hasExcessPathLength() + && Float.compare(mExcessPathLengthMeters, other.mExcessPathLengthMeters) != 0) { + return false; + } + + if (hasExcessPathLengthUncertainty() != other.hasExcessPathLengthUncertainty()) { + return false; + } + if (hasExcessPathLengthUncertainty() && Float.compare(mExcessPathLengthUncertaintyMeters, + other.mExcessPathLengthUncertaintyMeters) != 0) { + return false; + } + + if (hasReflectingPlane() != other.hasReflectingPlane()) { + return false; + } + if (hasReflectingPlane() + && !mReflectingPlane.equals(other.mReflectingPlane)) { + return false; + } + return true; } @Override - public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeInt(mSingleSatCorrectionFlags); - parcel.writeInt(mConstellationType); - parcel.writeInt(mSatId); - parcel.writeFloat(mCarrierFrequencyHz); - parcel.writeFloat(mProbSatIsLos); - parcel.writeFloat(mExcessPathLengthMeters); - parcel.writeFloat(mExcessPathLengthUncertaintyMeters); + public int hashCode() { + return Objects.hash(mSingleSatCorrectionFlags, + mConstellationType, + mSatId, + mCarrierFrequencyHz, + mProbSatIsLos, + mExcessPathLengthMeters, + mExcessPathLengthUncertaintyMeters, + mReflectingPlane); + } + + @NonNull + @Override + public String toString() { + StringBuilder builder = new StringBuilder("GnssSingleSatCorrection:["); + builder.append(" ConstellationType=").append(mConstellationType); + builder.append(" SatId=").append(mSatId); + builder.append(" CarrierFrequencyHz=").append(mCarrierFrequencyHz); + if (hasValidSatelliteLineOfSight()) { + builder.append(" ProbSatIsLos=").append(mProbSatIsLos); + } + if (hasExcessPathLength()) { + builder.append(" ExcessPathLengthMeters=").append(mExcessPathLengthMeters); + } + if (hasExcessPathLengthUncertainty()) { + builder.append(" ExcessPathLengthUncertaintyMeters=").append( + mExcessPathLengthUncertaintyMeters); + } if (hasReflectingPlane()) { - mReflectingPlane.writeToParcel(parcel, flags); + builder.append(" ReflectingPlane=").append(mReflectingPlane); } + builder.append(']'); + return builder.toString(); } /** Builder for {@link GnssSingleSatCorrection} */ @@ -332,6 +411,7 @@ public final class GnssSingleSatCorrection implements Parcelable { /** Sets the Satellite ID defined in the ICD of the given constellation. */ @NonNull public Builder setSatelliteId(@IntRange(from = 0) int satId) { + Preconditions.checkArgumentNonnegative(satId, "satId should be non-negative."); mSatId = satId; return this; } @@ -339,6 +419,8 @@ public final class GnssSingleSatCorrection implements Parcelable { /** Sets the Carrier frequency in Hz. */ @NonNull public Builder setCarrierFrequencyHz( @FloatRange(from = 0.0f, fromInclusive = false) float carrierFrequencyHz) { + Preconditions.checkArgument( + carrierFrequencyHz >= 0, "carrierFrequencyHz should be non-negative."); mCarrierFrequencyHz = carrierFrequencyHz; return this; } @@ -352,8 +434,18 @@ public final class GnssSingleSatCorrection implements Parcelable { Preconditions.checkArgumentInRange( probSatIsLos, 0, 1, "probSatIsLos should be between 0 and 1."); mProbSatIsLos = probSatIsLos; - mSingleSatCorrectionFlags = - (byte) (mSingleSatCorrectionFlags | HAS_PROB_SAT_IS_LOS_MASK); + mSingleSatCorrectionFlags |= HAS_PROB_SAT_IS_LOS_MASK; + return this; + } + + /** + * Clears the line-of-sight probability of the satellite at the given location. + * + * <p>This is to negate {@link #setProbabilityLineOfSight} call. + */ + @NonNull public Builder clearProbabilityLineOfSight() { + mProbSatIsLos = 0; + mSingleSatCorrectionFlags &= ~HAS_PROB_SAT_IS_LOS_MASK; return this; } @@ -363,18 +455,42 @@ public final class GnssSingleSatCorrection implements Parcelable { */ @NonNull public Builder setExcessPathLengthMeters( @FloatRange(from = 0.0f) float excessPathLengthMeters) { + Preconditions.checkArgument(excessPathLengthMeters >= 0, + "excessPathLengthMeters should be non-negative."); mExcessPathLengthMeters = excessPathLengthMeters; - mSingleSatCorrectionFlags = - (byte) (mSingleSatCorrectionFlags | HAS_EXCESS_PATH_LENGTH_MASK); + mSingleSatCorrectionFlags |= HAS_EXCESS_PATH_LENGTH_MASK; + return this; + } + + /** + * Clears the Excess path length. + * + * <p>This is to negate {@link #setExcessPathLengthMeters} call. + */ + @NonNull public Builder clearExcessPathLengthMeters() { + mExcessPathLengthMeters = 0; + mSingleSatCorrectionFlags &= ~HAS_EXCESS_PATH_LENGTH_MASK; return this; } /** Sets the error estimate (1-sigma) for the Excess path length estimate */ @NonNull public Builder setExcessPathLengthUncertaintyMeters( @FloatRange(from = 0.0f) float excessPathLengthUncertaintyMeters) { + Preconditions.checkArgument(excessPathLengthUncertaintyMeters >= 0, + "excessPathLengthUncertaintyMeters should be non-negative."); mExcessPathLengthUncertaintyMeters = excessPathLengthUncertaintyMeters; - mSingleSatCorrectionFlags = - (byte) (mSingleSatCorrectionFlags | HAS_EXCESS_PATH_LENGTH_UNC_MASK); + mSingleSatCorrectionFlags |= HAS_EXCESS_PATH_LENGTH_UNC_MASK; + return this; + } + + /** + * Clears the error estimate (1-sigma) for the Excess path length estimate + * + * <p>This is to negate {@link #setExcessPathLengthUncertaintyMeters} call. + */ + @NonNull public Builder clearExcessPathLengthUncertaintyMeters() { + mExcessPathLengthUncertaintyMeters = 0; + mSingleSatCorrectionFlags &= ~HAS_EXCESS_PATH_LENGTH_UNC_MASK; return this; } @@ -382,18 +498,23 @@ public final class GnssSingleSatCorrection implements Parcelable { @NonNull public Builder setReflectingPlane(@Nullable GnssReflectingPlane reflectingPlane) { mReflectingPlane = reflectingPlane; if (reflectingPlane != null) { - mSingleSatCorrectionFlags = - (byte) (mSingleSatCorrectionFlags | HAS_REFLECTING_PLANE_MASK); + mSingleSatCorrectionFlags |= HAS_REFLECTING_PLANE_MASK; } else { - mSingleSatCorrectionFlags = - (byte) (mSingleSatCorrectionFlags & ~HAS_REFLECTING_PLANE_MASK); + mSingleSatCorrectionFlags &= ~HAS_REFLECTING_PLANE_MASK; } return this; } /** Builds a {@link GnssSingleSatCorrection} instance as specified by this builder. */ @NonNull public GnssSingleSatCorrection build() { - return new GnssSingleSatCorrection(this); + return new GnssSingleSatCorrection(mSingleSatCorrectionFlags, + mConstellationType, + mSatId, + mCarrierFrequencyHz, + mProbSatIsLos, + mExcessPathLengthMeters, + mExcessPathLengthUncertaintyMeters, + mReflectingPlane); } } } diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 9993ce9dec72..85e49cc5430b 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -1187,6 +1187,9 @@ public final class AudioAttributes implements Parcelable { case AudioSystem.STREAM_ACCESSIBILITY: mContentType = CONTENT_TYPE_SPEECH; break; + case AudioSystem.STREAM_ASSISTANT: + mContentType = CONTENT_TYPE_SPEECH; + break; default: Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes"); } @@ -1611,6 +1614,8 @@ public final class AudioAttributes implements Parcelable { return USAGE_VOICE_COMMUNICATION_SIGNALLING; case AudioSystem.STREAM_ACCESSIBILITY: return USAGE_ASSISTANCE_ACCESSIBILITY; + case AudioSystem.STREAM_ASSISTANT: + return USAGE_ASSISTANT; case AudioSystem.STREAM_TTS: default: return USAGE_UNKNOWN; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5c4211fdff24..21fc6eccc67b 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1204,7 +1204,8 @@ public class AudioManager { * * @hide */ - @UnsupportedAppUsage + @SystemApi + @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int streamType) { final IAudioService service = getService(); try { diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java index c91275941200..1f89f9990e75 100644 --- a/media/java/android/media/audiopolicy/AudioMixingRule.java +++ b/media/java/android/media/audiopolicy/AudioMixingRule.java @@ -145,10 +145,8 @@ public class AudioMixingRule { final int match_rule = mRule & ~RULE_EXCLUSION_MASK; switch (match_rule) { case RULE_MATCH_ATTRIBUTE_USAGE: - dest.writeInt(mAttr.getSystemUsage()); - break; case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: - dest.writeInt(mAttr.getCapturePreset()); + mAttr.writeToParcel(dest, AudioAttributes.FLATTEN_TAGS/*flags*/); break; case RULE_MATCH_UID: case RULE_MATCH_USERID: @@ -266,12 +264,14 @@ public class AudioMixingRule { public boolean isForCallRedirection() { for (AudioMixMatchCriterion criterion : mCriteria) { if (criterion.mAttr != null - && (criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE - && criterion.mAttr.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION) + && criterion.mAttr.isForCallRedirection() + && ((criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE + && (criterion.mAttr.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION + || criterion.mAttr.getUsage() + == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)) || (criterion.mRule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET - && criterion.mAttr.getCapturePreset() - == MediaRecorder.AudioSource.VOICE_COMMUNICATION) - && criterion.mAttr.isForCallRedirection()) { + && (criterion.mAttr.getCapturePreset() + == MediaRecorder.AudioSource.VOICE_COMMUNICATION)))) { return true; } } @@ -713,19 +713,8 @@ public class AudioMixingRule { Integer intProp = null; switch (match_rule) { case RULE_MATCH_ATTRIBUTE_USAGE: - int usage = in.readInt(); - if (AudioAttributes.isSystemUsage(usage)) { - attr = new AudioAttributes.Builder() - .setSystemUsage(usage).build(); - } else { - attr = new AudioAttributes.Builder() - .setUsage(usage).build(); - } - break; case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: - int preset = in.readInt(); - attr = new AudioAttributes.Builder() - .setInternalCapturePreset(preset).build(); + attr = AudioAttributes.CREATOR.createFromParcel(in); break; case RULE_MATCH_UID: case RULE_MATCH_USERID: diff --git a/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl b/media/java/android/media/tv/AdRequest.aidl index 69f479c683d1..ebfd748f0593 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl +++ b/media/java/android/media/tv/AdRequest.aidl @@ -13,11 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.telephony.euicc; -import android.content.Intent; +package android.media.tv; -/** @hide */ -oneway interface IResultCallback { - void onComplete(int resultCode, in Intent resultIntent); -} +parcelable AdRequest; diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java new file mode 100644 index 000000000000..536baf264dfb --- /dev/null +++ b/media/java/android/media/tv/AdRequest.java @@ -0,0 +1,135 @@ +/* + * 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.media.tv; + +import android.annotation.NonNull; +import android.annotation.StringDef; +import android.os.Bundle; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** @hide */ +public final class AdRequest implements Parcelable { + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "REQUEST_TYPE_", value = { + REQUEST_TYPE_START, + REQUEST_TYPE_STOP + }) + public @interface RequestType {} + + public static final String REQUEST_TYPE_START = "START"; + public static final String REQUEST_TYPE_STOP = "STOP"; + + public static final @NonNull Parcelable.Creator<AdRequest> CREATOR = + new Parcelable.Creator<AdRequest>() { + @Override + public AdRequest createFromParcel(Parcel source) { + return new AdRequest(source); + } + + @Override + public AdRequest[] newArray(int size) { + return new AdRequest[size]; + } + }; + + private final int mId; + private final @RequestType String mRequestType; + private final ParcelFileDescriptor mFileDescriptor; + private final long mStartTime; + private final long mStopTime; + private final long mEchoInterval; + private final String mMediaFileType; + private final Bundle mMetadata; + + public AdRequest(int id, @RequestType String requestType, ParcelFileDescriptor fileDescriptor, + long startTime, long stopTime, long echoInterval, String mediaFileType, + Bundle metadata) { + mId = id; + mRequestType = requestType; + mFileDescriptor = fileDescriptor; + mStartTime = startTime; + mStopTime = stopTime; + mEchoInterval = echoInterval; + mMediaFileType = mediaFileType; + mMetadata = metadata; + } + + private AdRequest(Parcel source) { + mId = source.readInt(); + mRequestType = source.readString(); + mFileDescriptor = source.readFileDescriptor(); + mStartTime = source.readLong(); + mStopTime = source.readLong(); + mEchoInterval = source.readLong(); + mMediaFileType = source.readString(); + mMetadata = source.readBundle(); + } + + public int getId() { + return mId; + } + + public @RequestType String getRequestType() { + return mRequestType; + } + + public ParcelFileDescriptor getFileDescriptor() { + return mFileDescriptor; + } + + public long getStartTime() { + return mStartTime; + } + + public long getStopTime() { + return mStopTime; + } + + public long getEchoInterval() { + return mEchoInterval; + } + + public String getMediaFileType() { + return mMediaFileType; + } + + public Bundle getMetadata() { + return mMetadata; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeString(mRequestType); + mFileDescriptor.writeToParcel(dest, flags); + dest.writeLong(mStartTime); + dest.writeLong(mStopTime); + dest.writeLong(mEchoInterval); + dest.writeString(mMediaFileType); + dest.writeBundle(mMetadata); + } +} diff --git a/media/java/android/media/tv/AdResponse.aidl b/media/java/android/media/tv/AdResponse.aidl new file mode 100644 index 000000000000..9c09a0a02d05 --- /dev/null +++ b/media/java/android/media/tv/AdResponse.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.media.tv; + +parcelable AdResponse; diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java new file mode 100644 index 000000000000..28cf5ac5b2b2 --- /dev/null +++ b/media/java/android/media/tv/AdResponse.java @@ -0,0 +1,96 @@ +/* + * 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.media.tv; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** @hide */ +public final class AdResponse implements Parcelable { + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "RESPONSE_TYPE_", value = { + RESPONSE_TYPE_PLAYING, + RESPONSE_TYPE_FINISHED, + RESPONSE_TYPE_STOPPED, + RESPONSE_TYPE_ERROR + }) + public @interface ResponseType {} + + public static final String RESPONSE_TYPE_PLAYING = "PLAYING"; + public static final String RESPONSE_TYPE_FINISHED = "FINISHED"; + public static final String RESPONSE_TYPE_STOPPED = "STOPPED"; + public static final String RESPONSE_TYPE_ERROR = "ERROR"; + + public static final @NonNull Parcelable.Creator<AdResponse> CREATOR = + new Parcelable.Creator<AdResponse>() { + @Override + public AdResponse createFromParcel(Parcel source) { + return new AdResponse(source); + } + + @Override + public AdResponse[] newArray(int size) { + return new AdResponse[size]; + } + }; + + private final int mId; + private final @ResponseType String mResponseType; + private final Long mElapsedTime; + + public AdResponse(int id, @ResponseType String responseType, @Nullable Long elapsedTime) { + mId = id; + mResponseType = responseType; + mElapsedTime = elapsedTime; + } + + private AdResponse(Parcel source) { + mId = source.readInt(); + mResponseType = source.readString(); + mElapsedTime = (Long) source.readValue(Long.class.getClassLoader()); + } + + public int getId() { + return mId; + } + + public @ResponseType String getResponseType() { + return mResponseType; + } + + public Long getElapsedTime() { + return mElapsedTime; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeString(mResponseType); + dest.writeValue(mElapsedTime); + } +} diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index de13fc1b9149..f4f55e44255e 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -17,6 +17,7 @@ package android.media.tv; import android.content.ComponentName; +import android.media.tv.AdResponse; import android.media.tv.AitInfo; import android.media.tv.BroadcastInfoResponse; import android.media.tv.ITvInputSession; @@ -54,4 +55,7 @@ oneway interface ITvInputClient { // For broadcast info void onBroadcastInfoResponse(in BroadcastInfoResponse response, int seq); + + // For ad response + void onAdResponse(in AdResponse response, int seq); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index a6e02174e6fd..d34e6360d95f 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.media.PlaybackParams; +import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.media.tv.DvbDeviceInfo; import android.media.tv.ITvInputClient; @@ -106,6 +107,9 @@ interface ITvInputManager { void requestBroadcastInfo(in IBinder sessionToken, in BroadcastInfoRequest request, int userId); void removeBroadcastInfo(in IBinder sessionToken, int id, int userId); + // For ad request + void requestAd(in IBinder sessionToken, in AdRequest request, int userId); + // For TV input hardware binding List<TvInputHardwareInfo> getHardwareList(); ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback, diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 025e6f1c2df8..f427501ff908 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -18,6 +18,7 @@ package android.media.tv; import android.graphics.Rect; import android.media.PlaybackParams; +import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.media.tv.TvTrackInfo; import android.net.Uri; @@ -67,4 +68,7 @@ oneway interface ITvInputSession { // For broadcast info void requestBroadcastInfo(in BroadcastInfoRequest request); void removeBroadcastInfo(int id); + + // For ad request + void requestAd(in AdRequest request); } diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 9a0aaa38f845..9830e78a7faa 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -16,6 +16,7 @@ package android.media.tv; +import android.media.tv.AdResponse; import android.media.tv.AitInfo; import android.media.tv.BroadcastInfoResponse; import android.media.tv.ITvInputSession; @@ -51,4 +52,7 @@ oneway interface ITvInputSessionCallback { // For broadcast info void onBroadcastInfoResponse(in BroadcastInfoResponse response); + + // For ad response + void onAdResponse(in AdResponse response); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index ef6ef91e6610..418ab2cd59af 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -73,6 +73,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_REQUEST_BROADCAST_INFO = 24; private static final int DO_REMOVE_BROADCAST_INFO = 25; private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 26; + private static final int DO_REQUEST_AD = 27; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -249,6 +250,10 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.setIAppNotificationEnabled((Boolean) msg.obj); break; } + case DO_REQUEST_AD: { + mTvInputSessionImpl.requestAd((AdRequest) msg.obj); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -414,6 +419,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_REMOVE_BROADCAST_INFO, requestId)); } + @Override + public void requestAd(AdRequest request) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REQUEST_AD, request)); + } + private final class TvInputEventReceiver extends InputEventReceiver { public TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index cde2f5a65df3..ad86002f862d 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -894,6 +894,19 @@ public final class TvInputManager { }); } } + + void postAdResponse(final AdResponse response) { + if (mSession.mIAppNotificationEnabled) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mSession.getIAppSession() != null) { + mSession.getIAppSession().notifyAdResponse(response); + } + } + }); + } + } } /** @@ -1317,6 +1330,18 @@ public final class TvInputManager { record.postBroadcastInfoResponse(response); } } + + @Override + public void onAdResponse(AdResponse response, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postAdResponse(response); + } + } }; ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() { @Override @@ -3033,6 +3058,18 @@ public final class TvInputManager { } } + public void requestAd(AdRequest request) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.requestAd(mToken, request, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final class InputEventHandler extends Handler { public static final int MSG_SEND_INPUT_EVENT = 1; public static final int MSG_TIMEOUT_INPUT_EVENT = 2; diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index ee33e5c43dde..bd5a343963df 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -849,7 +849,7 @@ public abstract class TvInputService extends Service { /** * Notifies response for broadcast info. * - * @param response + * @param response broadcast info response. * @hide */ public void notifyBroadcastInfoResponse(@NonNull final BroadcastInfoResponse response) { @@ -869,6 +869,29 @@ public abstract class TvInputService extends Service { }); } + /** + * Notifies response for advertisement. + * + * @param response advertisement response. + * @hide + */ + public void notifyAdResponse(@NonNull final AdResponse response) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyAdResponse"); + if (mSessionCallback != null) { + mSessionCallback.onAdResponse(response); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyAdResponse", e); + } + } + }); + } + private void notifyTimeShiftStartPositionChanged(final long timeMs) { executeOrPostRunnableOnMainThread(new Runnable() { @MainThread @@ -1038,6 +1061,8 @@ public abstract class TvInputService extends Service { public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume); /** + * called when broadcast info is requested. + * * @param request broadcast info request * @hide */ @@ -1051,6 +1076,15 @@ public abstract class TvInputService extends Service { } /** + * called when advertisement is requested. + * + * @param request advertisement request + * @hide + */ + public void onRequestAd(@NonNull AdRequest request) { + } + + /** * Tunes to a given channel. * * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called. @@ -1663,6 +1697,10 @@ public abstract class TvInputService extends Service { onRemoveBroadcastInfo(requestId); } + void requestAd(AdRequest request) { + onRequestAd(request); + } + /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl index 00c464f5e0ad..892a800ca68d 100644 --- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.graphics.Rect; +import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.net.Uri; import android.os.Bundle; @@ -41,4 +42,5 @@ oneway interface ITvIAppClient { void onRequestCurrentChannelLcn(int seq); void onRequestStreamVolume(int seq); void onRequestTrackInfoList(int seq); + void onAdRequest(in AdRequest request, int Seq); } diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl index 15a2eacee2a2..23201faa1c5a 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.graphics.Rect; +import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; import android.media.tv.interactive.ITvIAppClient; @@ -55,6 +56,7 @@ interface ITvIAppManager { int userId); void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response, int UserId); + void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId); void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame, int userId); @@ -63,4 +65,4 @@ interface ITvIAppManager { void registerCallback(in ITvIAppManagerCallback callback, int userId); void unregisterCallback(in ITvIAppManagerCallback callback, int userId); -}
\ No newline at end of file +} diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl index 4a85feff159a..52f9a874aca2 100644 --- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl @@ -19,6 +19,7 @@ package android.media.tv.interactive; import android.graphics.Rect; import android.media.tv.BroadcastInfoResponse; import android.net.Uri; +import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; import android.os.Bundle; @@ -44,8 +45,9 @@ oneway interface ITvIAppSession { void setSurface(in Surface surface); void dispatchSurfaceChanged(int format, int width, int height); void notifyBroadcastInfoResponse(in BroadcastInfoResponse response); + void notifyAdResponse(in AdResponse response); void createMediaView(in IBinder windowToken, in Rect frame); void relayoutMediaView(in Rect frame); void removeMediaView(); -}
\ No newline at end of file +} diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl index b09f787ef680..9b9e6afb786b 100644 --- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl @@ -17,9 +17,9 @@ package android.media.tv.interactive; import android.graphics.Rect; +import android.media.tv.AdRequest; import android.media.tv.BroadcastInfoRequest; import android.media.tv.interactive.ITvIAppSession; -import android.media.tv.BroadcastInfoRequest; import android.net.Uri; import android.os.Bundle; @@ -41,4 +41,5 @@ oneway interface ITvIAppSessionCallback { void onRequestCurrentChannelLcn(); void onRequestStreamVolume(); void onRequestTrackInfoList(); + void onAdRequest(in AdRequest request); } diff --git a/media/java/android/media/tv/interactive/TvIAppInfo.java b/media/java/android/media/tv/interactive/TvIAppInfo.java index 6d5e561e7e9b..79d94cce77fe 100644 --- a/media/java/android/media/tv/interactive/TvIAppInfo.java +++ b/media/java/android/media/tv/interactive/TvIAppInfo.java @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.annotation.NonNull; +import android.annotation.StringDef; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -35,6 +36,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -46,6 +49,21 @@ public final class TvIAppInfo implements Parcelable { private static final boolean DEBUG = false; private static final String TAG = "TvIAppInfo"; + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = { + INTERACTIVE_APP_TYPE_HBBTV, + INTERACTIVE_APP_TYPE_ATSC, + INTERACTIVE_APP_TYPE_GINGA, + }) + @interface InteractiveAppType {} + + /** HbbTV interactive app type */ + public static final String INTERACTIVE_APP_TYPE_HBBTV = "hbbtv"; + /** ATSC interactive app type */ + public static final String INTERACTIVE_APP_TYPE_ATSC = "atsc"; + /** Ginga interactive app type */ + public static final String INTERACTIVE_APP_TYPE_GINGA = "ginga"; + private final ResolveInfo mService; private final String mId; private List<String> mTypes = new ArrayList<>(); @@ -107,6 +125,13 @@ public final class TvIAppInfo implements Parcelable { } /** + * Gets supported interactive app types + */ + public List<String> getSupportedTypes() { + return new ArrayList<>(mTypes); + } + + /** * A convenience builder for creating {@link TvIAppInfo} objects. */ public static final class Builder { @@ -186,7 +211,7 @@ public final class TvIAppInfo implements Parcelable { CharSequence[] types = sa.getTextArray( com.android.internal.R.styleable.TvIAppService_supportedTypes); for (CharSequence cs : types) { - mTypes.add(cs.toString()); + mTypes.add(cs.toString().toLowerCase()); } sa.recycle(); diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java index 4c4dffb79404..d1fd1df74e1b 100644 --- a/media/java/android/media/tv/interactive/TvIAppManager.java +++ b/media/java/android/media/tv/interactive/TvIAppManager.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; import android.graphics.Rect; +import android.media.tv.AdRequest; +import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvInputManager; @@ -282,6 +284,18 @@ public final class TvIAppManager { } @Override + public void onAdRequest(AdRequest request, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postAdRequest(request); + } + } + + @Override public void onRequestCurrentChannelUri(int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); @@ -945,6 +959,23 @@ public final class TvIAppManager { } /** + * Notifies of any advertisement response passed in from TIS. + * + * @param response response passed in from TIS. + */ + public void notifyAdResponse(AdResponse response) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyAdResponse(mToken, response, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Releases this session. */ public void release() { @@ -1322,6 +1353,17 @@ public final class TvIAppManager { }); } + void postAdRequest(final AdRequest request) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mSession.getInputSession() != null) { + mSession.getInputSession().requestAd(request); + } + } + }); + } + void postSessionStateChanged(int state) { mHandler.post(new Runnable() { @Override diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java index a0f8413ac29d..1480ff643d99 100644 --- a/media/java/android/media/tv/interactive/TvIAppService.java +++ b/media/java/android/media/tv/interactive/TvIAppService.java @@ -27,6 +27,8 @@ import android.content.Context; import android.content.Intent; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.media.tv.AdRequest; +import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; @@ -434,6 +436,13 @@ public abstract class TvIAppService extends Service { } /** + * Called when an advertisement response is received. + * @hide + */ + public void onAdResponse(AdResponse response) { + } + + /** * TODO: JavaDoc of APIs related to input events. * @hide */ @@ -703,6 +712,29 @@ public abstract class TvIAppService extends Service { }); } + /** + * requests an advertisement request to be processed by the related TV input. + * @param request advertisement request + */ + public void requestAd(@NonNull final AdRequest request) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestAd (id=" + request.getId() + ")"); + } + if (mSessionCallback != null) { + mSessionCallback.onAdRequest(request); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestAd", e); + } + } + }); + } + void startIApp() { onStartIApp(); } @@ -784,6 +816,16 @@ public abstract class TvIAppService extends Service { } /** + * Calls {@link #onAdResponse}. + */ + void notifyAdResponse(AdResponse response) { + if (DEBUG) { + Log.d(TAG, "notifyAdResponse (requestId=" + response.getId() + ")"); + } + onAdResponse(response); + } + + /** * Notifies when the session state is changed. * @param state the current state. */ @@ -1142,6 +1184,11 @@ public abstract class TvIAppService extends Service { } @Override + public void notifyAdResponse(AdResponse response) { + mSessionImpl.notifyAdResponse(response); + } + + @Override public void createMediaView(IBinder windowToken, Rect frame) { mSessionImpl.createMediaView(windowToken, frame); } diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvIAppView.java index 62ec4e31931f..1ce14ae98351 100644 --- a/media/java/android/media/tv/interactive/TvIAppView.java +++ b/media/java/android/media/tv/interactive/TvIAppView.java @@ -113,8 +113,12 @@ public class TvIAppView extends ViewGroup { this(context, null, 0); } + public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + public TvIAppView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, /* attrs = */null, /* defStyleAttr = */0); + super(context, attrs, defStyleAttr); int sourceResId = Resources.getAttributeSetSourceResId(attrs); if (sourceResId != Resources.ID_NULL) { Log.d(TAG, "Build local AttributeSet"); @@ -127,7 +131,7 @@ public class TvIAppView extends ViewGroup { } mDefStyleAttr = defStyleAttr; resetSurfaceView(); - mTvIAppManager = (TvIAppManager) getContext().getSystemService("tv_interactive_app"); + mTvIAppManager = (TvIAppManager) getContext().getSystemService(Context.TV_IAPP_SERVICE); } /** diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 7c08913c82cd..4128abf03e07 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -19,6 +19,7 @@ package android.media.tv.tuner; import android.annotation.BytesLong; import android.annotation.CallbackExecutor; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -688,6 +689,8 @@ public class Tuner implements AutoCloseable { private native Filter nativeOpenFilter(int type, int subType, long bufferSize); private native TimeFilter nativeOpenTimeFilter(); private native String nativeGetFrontendHardwareInfo(); + private native int nativeSetMaxNumberOfFrontends(int frontendType, int maxNumber); + private native int nativeGetMaxNumberOfFrontends(int frontendType); private native Lnb nativeOpenLnbByHandle(int handle); private native Lnb nativeOpenLnbByName(String name); @@ -1291,7 +1294,7 @@ public class Tuner implements AutoCloseable { * @throws IllegalStateException if there is no active frontend currently. */ @Nullable - public String getCurrentFrontendHardwardInfo() { + public String getCurrentFrontendHardwareInfo() { mFrontendLock.lock(); try { if (!TunerVersionChecker.checkHigherOrEqualVersionTo( @@ -1307,6 +1310,55 @@ public class Tuner implements AutoCloseable { } } + /** + * Sets the maximum usable frontends number of a given frontend type. It is used to enable or + * disable frontends when cable connection status is changed by user. + * + * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would return + * {@link RESULT_UNAVAILABLE}. Use {@link TunerVersionChecker#getTunerVersion()} to check the + * version. + * + * @param frontendType the {@link android.media.tv.tuner.frontend.FrontendSettings.Type} which + * the maximum usable number will be set. + * @param maxNumber the new maximum usable number. + * @return result status of the operation. + */ + @Result + public int setMaxNumberOfFrontends( + @FrontendSettings.Type int frontendType, @IntRange(from = 0) int maxNumber) { + if (!TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) { + return RESULT_UNAVAILABLE; + } + if (maxNumber < 0) { + return RESULT_INVALID_ARGUMENT; + } + int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber); + if (res == RESULT_SUCCESS) { + // TODO: b/211778848 Update Tuner Resource Manager. + } + return res; + } + + /** + * Get the maximum usable frontends number of a given frontend type. + * + * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would return + * {@code -1}. Use {@link TunerVersionChecker#getTunerVersion()} to check the version. + * + * @param frontendType the {@link android.media.tv.tuner.frontend.FrontendSettings.Type} which + * the maximum usable number will be queried. + * @return the maximum usable number of the queried frontend type. + */ + @IntRange(from = -1) + public int getMaxNumberOfFrontends(@FrontendSettings.Type int frontendType) { + if (!TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) { + return -1; + } + return nativeGetMaxNumberOfFrontends(frontendType); + } + /** @hide */ public FrontendInfo getFrontendInfoById(int id) { mFrontendLock.lock(); @@ -1382,7 +1434,7 @@ public class Tuner implements AutoCloseable { } } - private void onUnLocked() { + private void onUnlocked() { Log.d(TAG, "Wrote Stats Log for unlocked event from scanning."); FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED); @@ -1392,7 +1444,7 @@ public class Tuner implements AutoCloseable { mScanCallbackExecutor.execute(() -> { synchronized (mScanCallbackLock) { if (mScanCallback != null) { - mScanCallback.onUnLocked(); + mScanCallback.onUnlocked(); } } }); diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java index 6bbc13fe66f4..68f3c1bce01b 100644 --- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -37,7 +37,7 @@ public interface ScanCallback { void onLocked(); /** Scan unlocked the signal. */ - default void onUnLocked() {} + default void onUnlocked() {} /** Scan stopped. */ void onScanStopped(); diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index f5606bc3539f..c601649b74a1 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -1044,7 +1044,7 @@ void FrontendClientCallbackImpl::executeOnScanMessage( } else { env->CallVoidMethod( frontend, - env->GetMethodID(clazz, "onUnLocked", "()V")); + env->GetMethodID(clazz, "onUnlocked", "()V")); } break; } @@ -1577,6 +1577,24 @@ Result JTuner::getFrontendHardwareInfo(string &info) { return mFeClient->getHardwareInfo(info); } +jint JTuner::setMaxNumberOfFrontends(int32_t type, int32_t maxNumber) { + if (mTunerClient == nullptr) { + ALOGE("tuner is not initialized"); + return (jint)Result::INVALID_STATE; + } + + return (jint)mTunerClient->setMaxNumberOfFrontends(static_cast<FrontendType>(type), maxNumber); +} + +int32_t JTuner::getMaxNumberOfFrontends(int32_t type) { + if (mTunerClient == nullptr) { + ALOGE("tuner is not initialized"); + return -1; + } + + return mTunerClient->getMaxNumberOfFrontends(static_cast<FrontendType>(type)); +} + jobject JTuner::openLnbByHandle(int handle) { if (mTunerClient == nullptr) { return nullptr; @@ -4281,6 +4299,17 @@ static jstring android_media_tv_Tuner_get_frontend_hardware_info(JNIEnv *env, jo return env->NewStringUTF(info.data()); } +static jint android_media_tv_Tuner_set_maximum_frontends(JNIEnv *env, jobject thiz, jint type, + jint maxNumber) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->setMaxNumberOfFrontends(type, maxNumber); +} + +static jint android_media_tv_Tuner_get_maximum_frontends(JNIEnv *env, jobject thiz, jint type) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->getMaxNumberOfFrontends(type); +} + static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->closeFrontend(); @@ -4592,6 +4621,10 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_shared_filter}, { "nativeGetFrontendHardwareInfo","()Ljava/lang/String;", (void *)android_media_tv_Tuner_get_frontend_hardware_info }, + { "nativeSetMaxNumberOfFrontends", "(II)I", + (void *)android_media_tv_Tuner_set_maximum_frontends }, + { "nativeGetMaxNumberOfFrontends", "(I)I", + (void *)android_media_tv_Tuner_get_maximum_frontends }, }; static const JNINativeMethod gFilterMethods[] = { diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 4cad92b7d0f8..f1b31e3520b1 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -201,6 +201,8 @@ struct JTuner : public RefBase { jint closeFrontend(); jint closeDemux(); Result getFrontendHardwareInfo(string& info); + jint setMaxNumberOfFrontends(int32_t frontendType, int32_t maxNumber); + int32_t getMaxNumberOfFrontends(int32_t frontendType); jweak getObject(); diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp index f917f016939a..3c8fdfe682af 100644 --- a/media/jni/tuner/TunerClient.cpp +++ b/media/jni/tuner/TunerClient.cpp @@ -194,4 +194,23 @@ Result TunerClient::setLna(bool bEnable) { return Result::INVALID_STATE; } +Result TunerClient::setMaxNumberOfFrontends(FrontendType frontendType, int32_t maxNumber) { + if (mTunerService != nullptr) { + Status s = mTunerService->setMaxNumberOfFrontends(frontendType, maxNumber); + return ClientHelper::getServiceSpecificErrorCode(s); + } + + return Result::INVALID_STATE; +} + +int TunerClient::getMaxNumberOfFrontends(FrontendType frontendType) { + if (mTunerService != nullptr) { + int32_t maxNumber; + mTunerService->getMaxNumberOfFrontends(frontendType, &maxNumber); + return maxNumber; + } + + return -1; +} + } // namespace android diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h index 37b8ee1526a5..a9f37e6df3aa 100644 --- a/media/jni/tuner/TunerClient.h +++ b/media/jni/tuner/TunerClient.h @@ -32,6 +32,7 @@ using Status = ::ndk::ScopedAStatus; using ::aidl::android::hardware::tv::tuner::DemuxCapabilities; using ::aidl::android::hardware::tv::tuner::FrontendInfo; +using ::aidl::android::hardware::tv::tuner::FrontendType; using ::aidl::android::hardware::tv::tuner::Result; using ::aidl::android::media::tv::tuner::ITunerService; @@ -132,6 +133,21 @@ public: */ Result setLna(bool bEnable); + /** + * Set the maximum frontend number of a given frontend type. + * + * @param frontendType the frontend type which maximum number will be set. + * @param maxNumber the new maximum number. + */ + Result setMaxNumberOfFrontends(FrontendType frontendType, int32_t maxNumber); + + /** + * Get the maximum frontend number of a given frontend type. + * + * @param frontendType the frontend type which maximum number will be queried. + */ + int getMaxNumberOfFrontends(FrontendType frontendType); + private: /** * An AIDL Tuner Service Singleton assigned at the first time the Tuner Client diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml index 03606bac6840..90390116df06 100644 --- a/media/packages/BluetoothMidiService/AndroidManifest.xml +++ b/media/packages/BluetoothMidiService/AndroidManifest.xml @@ -20,7 +20,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.android.bluetoothmidiservice" > - <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> + <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> diff --git a/media/packages/BluetoothMidiService/AndroidManifestBase.xml b/media/packages/BluetoothMidiService/AndroidManifestBase.xml index bfb05469adb9..5a900c794dd1 100644 --- a/media/packages/BluetoothMidiService/AndroidManifestBase.xml +++ b/media/packages/BluetoothMidiService/AndroidManifestBase.xml @@ -19,7 +19,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.bluetoothmidiservice" > - <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> + <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" /> <application android:label="BluetoothMidi" android:defaultToDeviceProtectedStorage="true" diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp index 2b81200f0079..d80317bb8c60 100644 --- a/omapi/aidl/Android.bp +++ b/omapi/aidl/Android.bp @@ -28,8 +28,5 @@ aidl_interface { rust: { enabled: true, }, - ndk: { - separate_platform_variant: false, - }, }, } diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java index 216a4a0987f5..f684a4d9d89a 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java @@ -24,13 +24,15 @@ import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; import android.os.RemoteException; -import android.util.IntArray; import android.util.Log; +import com.android.net.module.util.CollectionUtils; + import dalvik.system.CloseGuard; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; /** * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects @@ -568,7 +570,7 @@ public final class NetworkStats implements AutoCloseable { // the filtering logic below can be removed. int[] uids = mSession.getRelevantUids(); // Filtering of uids with empty history. - IntArray filteredUids = new IntArray(uids.length); + final ArrayList<Integer> filteredUids = new ArrayList<>(); for (int uid : uids) { try { NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid, @@ -581,7 +583,7 @@ public final class NetworkStats implements AutoCloseable { Log.w(TAG, "Error while getting history of uid " + uid, e); } } - mUids = filteredUids.toArray(); + mUids = CollectionUtils.toIntArray(filteredUids); mUidOrUidIndex = -1; stepHistory(); } diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index 2b9560e7ec52..a316b8a617b7 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -47,7 +47,6 @@ import android.os.Messenger; import android.os.RemoteException; import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.util.DataUnit; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -119,7 +118,7 @@ public class NetworkStatsManager { * is reached. * @hide */ - public static final long MIN_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(2); + public static final long MIN_THRESHOLD_BYTES = 2 * 1_048_576L; // 2MiB private final Context mContext; private final INetworkStatsService mService; diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java index eb8f43e3d073..8f1115e065dd 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java @@ -21,7 +21,6 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import android.annotation.Nullable; import android.content.Context; import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; import android.service.NetworkIdentityProto; import android.telephony.Annotation.NetworkType; import android.util.proto.ProtoOutputStream; @@ -228,11 +227,11 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities()); if (legacyType == TYPE_WIFI) { - networkId = snapshot.getNetworkCapabilities().getSsid(); - if (networkId == null) { - final WifiManager wifi = context.getSystemService(WifiManager.class); - final WifiInfo info = wifi.getConnectionInfo(); - networkId = info != null ? info.getSSID() : null; + final TransportInfo transportInfo = snapshot.getNetworkCapabilities() + .getTransportInfo(); + if (transportInfo instanceof WifiInfo) { + final WifiInfo info = (WifiInfo) transportInfo; + networkId = info != null ? info.getCurrentNetworkKey() : null; } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java index 1986b83b12d8..b00fea4de269 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java @@ -31,7 +31,7 @@ import android.os.SystemClock; import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; +import com.android.net.module.util.CollectionUtils; import libcore.util.EmptyArray; @@ -83,10 +83,7 @@ public final class NetworkStats implements Parcelable { */ // TODO: Rename TAG_ALL to TAG_ANY. public static final int TAG_ALL = -1; - /** - * {@link #set} value for all sets combined, not including debug sets. - * @hide - */ + /** {@link #set} value for all sets combined, not including debug sets. */ public static final int SET_ALL = -1; /** {@link #set} value where background data is accounted. */ public static final int SET_DEFAULT = 0; @@ -114,9 +111,6 @@ public final class NetworkStats implements Parcelable { SET_ALL, SET_DEFAULT, SET_FOREGROUND, - SET_DEBUG_START, - SET_DBG_VPN_IN, - SET_DBG_VPN_OUT }) public @interface State { } @@ -131,10 +125,7 @@ public final class NetworkStats implements Parcelable { // TODO: Rename TAG_NONE to TAG_ALL. public static final int TAG_NONE = 0; - /** - * {@link #metered} value to account for all metered states. - * @hide - */ + /** {@link #metered} value to account for all metered states. */ public static final int METERED_ALL = -1; /** {@link #metered} value where native, unmetered data is accounted. */ public static final int METERED_NO = 0; @@ -152,10 +143,7 @@ public final class NetworkStats implements Parcelable { } - /** - * {@link #roaming} value to account for all roaming states. - * @hide - */ + /** {@link #roaming} value to account for all roaming states. */ public static final int ROAMING_ALL = -1; /** {@link #roaming} value where native, non-roaming data is accounted. */ public static final int ROAMING_NO = 0; @@ -172,10 +160,7 @@ public final class NetworkStats implements Parcelable { public @interface Roaming { } - /** - * {@link #onDefaultNetwork} value to account for all default network states. - * @hide - */ + /** {@link #onDefaultNetwork} value to account for all default network states. */ public static final int DEFAULT_NETWORK_ALL = -1; /** {@link #onDefaultNetwork} value to account for usage while not the default network. */ public static final int DEFAULT_NETWORK_NO = 0; @@ -1185,7 +1170,7 @@ public final class NetworkStats implements Parcelable { * @hide */ public void removeUids(int[] uids) { - filter(e -> !ArrayUtils.contains(uids, e.uid)); + filter(e -> !CollectionUtils.contains(uids, e.uid)); } /** @@ -1218,7 +1203,7 @@ public final class NetworkStats implements Parcelable { filter(e -> (limitUid == UID_ALL || limitUid == e.uid) && (limitTag == TAG_ALL || limitTag == e.tag) && (limitIfaces == INTERFACES_ALL - || ArrayUtils.contains(limitIfaces, e.iface))); + || CollectionUtils.contains(limitIfaces, e.iface))); } /** diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java index 591605d952e9..b64fbdba9a01 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java @@ -17,6 +17,7 @@ package android.net; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_REMOVED; import static android.net.TrafficStats.UID_TETHERING; @@ -106,7 +107,7 @@ public final class NetworkStatsAccess { /** Returns the {@link NetworkStatsAccess.Level} for the given caller. */ public static @NetworkStatsAccess.Level int checkAccessLevel( - Context context, int callingUid, String callingPackage) { + Context context, int callingPid, int callingUid, String callingPackage) { final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class); final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); @@ -123,8 +124,12 @@ public final class NetworkStatsAccess { final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage); final int appId = UserHandle.getAppId(callingUid); + final boolean isNetworkStack = context.checkPermission( + android.Manifest.permission.NETWORK_STACK, callingPid, callingUid) + == PERMISSION_GRANTED; + if (hasCarrierPrivileges || isDeviceOwner - || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) { + || appId == Process.SYSTEM_UID || isNetworkStack) { // Carrier-privileged apps and device owners, and the system (including the // network stack) can access data usage for all apps on the device. return NetworkStatsAccess.Level.DEVICE; @@ -155,6 +160,8 @@ public final class NetworkStatsAccess { */ public static boolean isAccessibleToUser(int uid, int callerUid, @NetworkStatsAccess.Level int accessLevel) { + final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); + final int callerUserId = UserHandle.getUserHandleForUid(callerUid).getIdentifier(); switch (accessLevel) { case NetworkStatsAccess.Level.DEVICE: // Device-level access - can access usage for any uid. @@ -165,13 +172,13 @@ public final class NetworkStatsAccess { // anonymized uids return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED || uid == UID_TETHERING || uid == UID_ALL - || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid); + || userId == callerUserId; case NetworkStatsAccess.Level.USER: // User-level access - can access usage for any app running in the same user, along // with some special uids (system, removed, or tethering). return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED || uid == UID_TETHERING - || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid); + || userId == callerUserId; case NetworkStatsAccess.Level.DEFAULT: default: // Default access level - can only access one's own usage. @@ -185,8 +192,8 @@ public final class NetworkStatsAccess { AppOpsManager appOps = (AppOpsManager) context.getSystemService( Context.APP_OPS_SERVICE); - final int mode = appOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS, - callingUid, callingPackage); + final int mode = appOps.noteOp(AppOpsManager.OPSTR_GET_USAGE_STATS, + callingUid, callingPackage, null /* attributionTag */, null /* message */); if (mode == AppOpsManager.MODE_DEFAULT) { // The default behavior here is to check if PackageManager has given the app // permission. diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java index 8d1347e25bb6..7935d28f305d 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java @@ -40,21 +40,17 @@ import android.telephony.SubscriptionPlan; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.AtomicFile; -import android.util.IntArray; -import android.util.MathUtils; +import android.util.IndentingPrintWriter; +import android.util.Log; import android.util.Range; -import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastDataInput; import com.android.internal.util.FastDataOutput; import com.android.internal.util.FileRotator; -import com.android.internal.util.IndentingPrintWriter; - -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; +import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.NetworkStatsUtils; import libcore.io.IoUtils; @@ -196,11 +192,11 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel, final int callerUid) { - IntArray uids = new IntArray(); + final ArrayList<Integer> uids = new ArrayList<>(); for (int i = 0; i < mStats.size(); i++) { final Key key = mStats.keyAt(i); if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) { - int j = uids.binarySearch(key.uid); + int j = Collections.binarySearch(uids, new Integer(key.uid)); if (j < 0) { j = ~j; @@ -208,7 +204,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } } - return uids.toArray(); + return CollectionUtils.toIntArray(uids); } /** @@ -225,7 +221,8 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W // 180 days of history should be enough for anyone; if we end up needing // more, we'll dynamically grow the history object. - final int bucketEstimate = (int) MathUtils.constrain(((end - start) / mBucketDuration), 0, + final int bucketEstimate = (int) NetworkStatsUtils.constrain( + ((end - start) / mBucketDuration), 0, (180 * DateUtils.DAY_IN_MILLIS) / mBucketDuration); final NetworkStatsHistory combined = new NetworkStatsHistory( mBucketDuration, bucketEstimate, fields); @@ -316,7 +313,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W final long deltaTotal = combined.getTotalBytes() - beforeTotal; if (deltaTotal != 0) { - Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes"); + Log.d(TAG, "Augmented network usage by " + deltaTotal + " bytes"); } // Finally we can slice data as originally requested @@ -489,11 +486,11 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W private void write(DataOutput out) throws IOException { // cluster key lists grouped by ident - final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); + final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = new HashMap<>(); for (Key key : mStats.keySet()) { ArrayList<Key> keys = keysByIdent.get(key.ident); if (keys == null) { - keys = Lists.newArrayList(); + keys = new ArrayList<>(); keysByIdent.put(key.ident, keys); } keys.add(key); @@ -640,12 +637,12 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * {@link TrafficStats#UID_REMOVED}. */ public void removeUids(int[] uids) { - final ArrayList<Key> knownKeys = Lists.newArrayList(); + final ArrayList<Key> knownKeys = new ArrayList<>(); knownKeys.addAll(mStats.keySet()); // migrate all UID stats into special "removed" bucket for (Key key : knownKeys) { - if (ArrayUtils.contains(uids, key.uid)) { + if (CollectionUtils.contains(uids, key.uid)) { // only migrate combined TAG_NONE history if (key.tag == TAG_NONE) { final NetworkStatsHistory uidHistory = mStats.get(key); @@ -672,7 +669,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } private ArrayList<Key> getSortedKeys() { - final ArrayList<Key> keys = Lists.newArrayList(); + final ArrayList<Key> keys = new ArrayList<>(); keys.addAll(mStats.keySet()); Collections.sort(keys); return keys; diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java index 3eef4ee6f829..428bc6df266a 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java @@ -28,7 +28,6 @@ import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; import static android.text.format.DateUtils.SECOND_IN_MILLIS; -import static com.android.internal.util.ArrayUtils.total; import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; import android.compat.annotation.UnsupportedAppUsage; @@ -37,10 +36,11 @@ import android.os.Parcel; import android.os.Parcelable; import android.service.NetworkStatsHistoryBucketProto; import android.service.NetworkStatsHistoryProto; -import android.util.MathUtils; +import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; -import com.android.internal.util.IndentingPrintWriter; +import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.NetworkStatsUtils; import libcore.util.EmptyArray; @@ -174,7 +174,7 @@ public class NetworkStatsHistory implements Parcelable { txPackets = new long[bucketStart.length]; operations = new long[bucketStart.length]; bucketCount = bucketStart.length; - totalBytes = total(rxBytes) + total(txBytes); + totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes); break; } case VERSION_ADD_PACKETS: @@ -189,7 +189,7 @@ public class NetworkStatsHistory implements Parcelable { txPackets = readVarLongArray(in); operations = readVarLongArray(in); bucketCount = bucketStart.length; - totalBytes = total(rxBytes) + total(txBytes); + totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes); break; } default: { @@ -267,7 +267,7 @@ public class NetworkStatsHistory implements Parcelable { } else { index -= 1; } - return MathUtils.constrain(index, 0, bucketCount - 1); + return NetworkStatsUtils.constrain(index, 0, bucketCount - 1); } /** @@ -281,7 +281,7 @@ public class NetworkStatsHistory implements Parcelable { } else { index += 1; } - return MathUtils.constrain(index, 0, bucketCount - 1); + return NetworkStatsUtils.constrain(index, 0, bucketCount - 1); } /** @@ -349,6 +349,9 @@ public class NetworkStatsHistory implements Parcelable { // create any buckets needed by this range ensureBuckets(start, end); + // Return fast if there is still no entry. This would typically happen when the start, + // end or duration are not valid values, e.g. start > end, negative duration value, etc. + if (bucketCount == 0) return; // distribute data usage into buckets long duration = end - start; @@ -560,6 +563,9 @@ public class NetworkStatsHistory implements Parcelable { entry.txPackets = txPackets != null ? 0 : UNKNOWN; entry.operations = operations != null ? 0 : UNKNOWN; + // Return fast if there is no entry. + if (bucketCount == 0) return entry; + final int startIndex = getIndexAfter(end); for (int i = startIndex; i >= 0; i--) { final long curStart = bucketStart[i]; diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java index 5da8e259d09d..e9084b019668 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java @@ -16,6 +16,7 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_MOBILE; @@ -35,12 +36,13 @@ import static android.net.NetworkStats.METERED_YES; import static android.net.NetworkStats.ROAMING_ALL; import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.ROAMING_YES; -import static android.net.wifi.WifiInfo.sanitizeSsid; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -49,7 +51,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; -import com.android.internal.util.ArrayUtils; +import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkIdentityUtils; import com.android.net.module.util.NetworkStatsUtils; @@ -57,6 +59,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -70,7 +73,7 @@ import java.util.TreeSet; * * @hide */ -// @SystemApi(client = MODULE_LIBRARIES) +@SystemApi(client = MODULE_LIBRARIES) public final class NetworkTemplate implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -213,11 +216,14 @@ public final class NetworkTemplate implements Parcelable { public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId, @NetworkType int ratType, int metered) { if (TextUtils.isEmpty(subscriberId)) { - return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null, null, - metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL, + return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null /* subscriberId */, + null /* matchSubscriberIds */, + new String[0] /* matchWifiNetworkKeys */, metered, ROAMING_ALL, + DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); } - return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[]{subscriberId}, null, + return new NetworkTemplate(MATCH_MOBILE, subscriberId, new String[] { subscriberId }, + new String[0] /* matchWifiNetworkKeys */, metered, ROAMING_ALL, DEFAULT_NETWORK_ALL, ratType, OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); } @@ -235,7 +241,7 @@ public final class NetworkTemplate implements Parcelable { /** * Template to match all metered {@link ConnectivityManager#TYPE_WIFI} networks, - * regardless of SSID. + * regardless of key of the wifi network. * * @hide */ @@ -255,33 +261,40 @@ public final class NetworkTemplate implements Parcelable { /** * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the - * given SSID. + * given key of the wifi network. * + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * to know details about the key. * @hide */ - public static NetworkTemplate buildTemplateWifi(@NonNull String networkId) { - Objects.requireNonNull(networkId); + public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) { + Objects.requireNonNull(wifiNetworkKey); return new NetworkTemplate(MATCH_WIFI, null /* subscriberId */, new String[] { null } /* matchSubscriberIds */, - networkId, METERED_ALL, ROAMING_ALL, + new String[] { wifiNetworkKey }, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL); } /** - * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given SSID, - * and IMSI. + * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given + * key of the wifi network and IMSI. * - * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code networkId} to get result regardless - * of SSID. + * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless + * of key of the wifi network. + * + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * to know details about the key. + * @param subscriberId the IMSI associated to this wifi network. * * @hide */ - public static NetworkTemplate buildTemplateWifi(@Nullable String networkId, + public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey, @Nullable String subscriberId) { return new NetworkTemplate(MATCH_WIFI, subscriberId, new String[] { subscriberId }, - networkId, METERED_ALL, ROAMING_ALL, - DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, + wifiNetworkKey != null + ? new String[] { wifiNetworkKey } : new String[0], + METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); } @@ -324,7 +337,9 @@ public final class NetworkTemplate implements Parcelable { public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) { Objects.requireNonNull(subscriberId); return new NetworkTemplate(MATCH_CARRIER, subscriberId, - new String[] { subscriberId }, null /* networkId */, METERED_YES, ROAMING_ALL, + new String[] { subscriberId }, + new String[0] /* matchWifiNetworkKeys */, + METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); } @@ -342,8 +357,8 @@ public final class NetworkTemplate implements Parcelable { */ private final String[] mMatchSubscriberIds; - // TODO: Change variable name to match the Api surface. - private final String mNetworkId; + @NonNull + private final String[] mMatchWifiNetworkKeys; // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. private final int mMetered; @@ -377,18 +392,19 @@ public final class NetworkTemplate implements Parcelable { /** @hide */ // TODO: Deprecate this constructor, mark it @UnsupportedAppUsage(maxTargetSdk = S) @UnsupportedAppUsage - public NetworkTemplate(int matchRule, String subscriberId, String networkId) { - this(matchRule, subscriberId, new String[] { subscriberId }, networkId); + public NetworkTemplate(int matchRule, String subscriberId, String wifiNetworkKey) { + this(matchRule, subscriberId, new String[] { subscriberId }, wifiNetworkKey); } /** @hide */ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, - String networkId) { + String wifiNetworkKey) { // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates // to metered networks. It is now possible to match mobile with any meteredness, but // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this //constructor passes METERED_YES for these types. - this(matchRule, subscriberId, matchSubscriberIds, networkId, + this(matchRule, subscriberId, matchSubscriberIds, + wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0], (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD) ? METERED_YES : METERED_ALL , ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); @@ -397,23 +413,25 @@ public final class NetworkTemplate implements Parcelable { /** @hide */ // TODO: Remove it after updating all of the caller. public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, - String networkId, int metered, int roaming, int defaultNetwork, int subType, + String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int subType, int oemManaged) { - this(matchRule, subscriberId, matchSubscriberIds, networkId, metered, roaming, - defaultNetwork, subType, oemManaged, + this(matchRule, subscriberId, matchSubscriberIds, + wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0], + metered, roaming, defaultNetwork, subType, oemManaged, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT); } /** @hide */ public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, - String networkId, int metered, int roaming, int defaultNetwork, int subType, - int oemManaged, int subscriberIdMatchRule) { + String[] matchWifiNetworkKeys, int metered, int roaming, + int defaultNetwork, int subType, int oemManaged, int subscriberIdMatchRule) { + Objects.requireNonNull(matchWifiNetworkKeys); mMatchRule = matchRule; mSubscriberId = subscriberId; // TODO: Check whether mMatchSubscriberIds = null or mMatchSubscriberIds = {null} when // mSubscriberId is null mMatchSubscriberIds = matchSubscriberIds; - mNetworkId = networkId; + mMatchWifiNetworkKeys = matchWifiNetworkKeys; mMetered = metered; mRoaming = roaming; mDefaultNetwork = defaultNetwork; @@ -431,7 +449,7 @@ public final class NetworkTemplate implements Parcelable { mMatchRule = in.readInt(); mSubscriberId = in.readString(); mMatchSubscriberIds = in.createStringArray(); - mNetworkId = in.readString(); + mMatchWifiNetworkKeys = in.createStringArray(); mMetered = in.readInt(); mRoaming = in.readInt(); mDefaultNetwork = in.readInt(); @@ -445,7 +463,7 @@ public final class NetworkTemplate implements Parcelable { dest.writeInt(mMatchRule); dest.writeString(mSubscriberId); dest.writeStringArray(mMatchSubscriberIds); - dest.writeString(mNetworkId); + dest.writeStringArray(mMatchWifiNetworkKeys); dest.writeInt(mMetered); dest.writeInt(mRoaming); dest.writeInt(mDefaultNetwork); @@ -471,9 +489,7 @@ public final class NetworkTemplate implements Parcelable { builder.append(", matchSubscriberIds=").append( Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds))); } - if (mNetworkId != null) { - builder.append(", networkId=").append(mNetworkId); - } + builder.append(", matchWifiNetworkKeys=").append(Arrays.toString(mMatchWifiNetworkKeys)); if (mMetered != METERED_ALL) { builder.append(", metered=").append(NetworkStats.meteredToString(mMetered)); } @@ -497,8 +513,8 @@ public final class NetworkTemplate implements Parcelable { @Override public int hashCode() { - return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming, - mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule); + return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys), + mMetered, mRoaming, mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule); } @Override @@ -507,13 +523,13 @@ public final class NetworkTemplate implements Parcelable { final NetworkTemplate other = (NetworkTemplate) obj; return mMatchRule == other.mMatchRule && Objects.equals(mSubscriberId, other.mSubscriberId) - && Objects.equals(mNetworkId, other.mNetworkId) && mMetered == other.mMetered && mRoaming == other.mRoaming && mDefaultNetwork == other.mDefaultNetwork && mSubType == other.mSubType && mOemManaged == other.mOemManaged - && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule; + && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule + && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys); } return false; } @@ -559,6 +575,7 @@ public final class NetworkTemplate implements Parcelable { /** * Get subscriber Id of the template. + * @hide */ @Nullable @UnsupportedAppUsage @@ -575,18 +592,19 @@ public final class NetworkTemplate implements Parcelable { } /** - * Get Wifi Network Key of the template. See {@link WifiInfo#getCurrentNetworkKey()}. + * Get the set of Wifi Network Keys of the template. + * See {@link WifiInfo#getCurrentNetworkKey()}. */ - @Nullable - public String getWifiNetworkKey() { - return mNetworkId; + @NonNull + public Set<String> getWifiNetworkKeys() { + return new ArraySet<>(Arrays.asList(mMatchWifiNetworkKeys)); } /** @hide */ - // TODO: Remove this and replace all callers with {@link #getWifiNetworkKey()}. + // TODO: Remove this and replace all callers with {@link #getWifiNetworkKeys()}. @Nullable public String getNetworkId() { - return mNetworkId; + return getWifiNetworkKeys().isEmpty() ? null : getWifiNetworkKeys().iterator().next(); } /** @@ -703,28 +721,33 @@ public final class NetworkTemplate implements Parcelable { */ public boolean matchesSubscriberId(@Nullable String subscriberId) { return mSubscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL - || ArrayUtils.contains(mMatchSubscriberIds, subscriberId); + || CollectionUtils.contains(mMatchSubscriberIds, subscriberId); } /** - * Check if network with matching SSID. Returns true when the SSID matches, or when - * {@code mNetworkId} is {@code WIFI_NETWORK_KEY_ALL}. + * Check if network matches key of the wifi network. + * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is + * empty. + * + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * to know details about the key. */ - private boolean matchesWifiNetworkId(@Nullable String networkId) { - return Objects.equals(mNetworkId, WIFI_NETWORK_KEY_ALL) - || Objects.equals(sanitizeSsid(mNetworkId), sanitizeSsid(networkId)); + private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) { + Objects.requireNonNull(wifiNetworkKey); + return CollectionUtils.isEmpty(mMatchWifiNetworkKeys) + || CollectionUtils.contains(mMatchWifiNetworkKeys, wifiNetworkKey); } /** - * Check if mobile network with matching IMSI. + * Check if mobile network matches IMSI. */ private boolean matchesMobile(NetworkIdentity ident) { if (ident.mType == TYPE_WIMAX) { // TODO: consider matching against WiMAX subscriber identity return true; } else { - return ident.mType == TYPE_MOBILE && !ArrayUtils.isEmpty(mMatchSubscriberIds) - && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId) + return ident.mType == TYPE_MOBILE && !CollectionUtils.isEmpty(mMatchSubscriberIds) + && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId) && matchesCollapsedRatType(ident); } } @@ -814,7 +837,7 @@ public final class NetworkTemplate implements Parcelable { switch (ident.mType) { case TYPE_WIFI: return matchesSubscriberId(ident.mSubscriberId) - && matchesWifiNetworkId(ident.mNetworkId); + && matchesWifiNetworkKey(ident.mNetworkId); default: return false; } @@ -835,8 +858,8 @@ public final class NetworkTemplate implements Parcelable { */ private boolean matchesCarrier(NetworkIdentity ident) { return ident.mSubscriberId != null - && !ArrayUtils.isEmpty(mMatchSubscriberIds) - && ArrayUtils.contains(mMatchSubscriberIds, ident.mSubscriberId); + && !CollectionUtils.isEmpty(mMatchSubscriberIds) + && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId); } private boolean matchesMobileWildcard(NetworkIdentity ident) { @@ -953,11 +976,13 @@ public final class NetworkTemplate implements Parcelable { if (template.mSubscriberId == null) return template; for (String[] merged : mergedList) { - if (ArrayUtils.contains(merged, template.mSubscriberId)) { + if (CollectionUtils.contains(merged, template.mSubscriberId)) { // Requested template subscriber is part of the merge group; return // a template that matches all merged subscribers. + final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys; return new NetworkTemplate(template.mMatchRule, merged[0], merged, - template.mNetworkId); + CollectionUtils.isEmpty(matchWifiNetworkKeys) + ? null : matchWifiNetworkKeys[0]); } } @@ -984,9 +1009,10 @@ public final class NetworkTemplate implements Parcelable { private final int mMatchRule; // Use a SortedSet to provide a deterministic order when fetching the first one. @NonNull - private final SortedSet<String> mMatchSubscriberIds = new TreeSet<>(); - @Nullable - private String mWifiNetworkKey; + private final SortedSet<String> mMatchSubscriberIds = + new TreeSet<>(Comparator.nullsFirst(Comparator.naturalOrder())); + @NonNull + private final SortedSet<String> mMatchWifiNetworkKeys = new TreeSet<>(); // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. private int mMetered; @@ -1006,7 +1032,6 @@ public final class NetworkTemplate implements Parcelable { assertRequestableMatchRule(matchRule); // Initialize members with default values. mMatchRule = matchRule; - mWifiNetworkKey = WIFI_NETWORK_KEY_ALL; mMetered = METERED_ALL; mRoaming = ROAMING_ALL; mDefaultNetwork = DEFAULT_NETWORK_ALL; @@ -1030,15 +1055,28 @@ public final class NetworkTemplate implements Parcelable { } /** - * Set the Wifi Network Key. + * Set the Wifi Network Keys. Calling this function with an empty set represents + * the intention of matching any Wifi Network Key. * - * @param wifiNetworkKey the Wifi Network Key, see {@link WifiInfo#getCurrentNetworkKey()}. - * Or null to match all networks. + * @param wifiNetworkKeys the list of Wifi Network Key, + * see {@link WifiInfo#getCurrentNetworkKey()}. + * Or an empty list to match all networks. + * Note that {@code getCurrentNetworkKey()} might get null key + * when wifi disconnects. However, the caller should never invoke + * this function with a null Wifi Network Key since such statistics + * never exists. * @return this builder. */ @NonNull - public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) { - mWifiNetworkKey = wifiNetworkKey; + public Builder setWifiNetworkKeys(@NonNull Set<String> wifiNetworkKeys) { + Objects.requireNonNull(wifiNetworkKeys); + for (String key : wifiNetworkKeys) { + if (key == null) { + throw new IllegalArgumentException("Null is not a valid key"); + } + } + mMatchWifiNetworkKeys.clear(); + mMatchWifiNetworkKeys.addAll(wifiNetworkKeys); return this; } @@ -1122,9 +1160,17 @@ public final class NetworkTemplate implements Parcelable { } private void assertRequestableParameters() { + validateWifiNetworkKeys(); // TODO: Check all the input are legitimate. } + private void validateWifiNetworkKeys() { + if (mMatchRule != MATCH_WIFI && !mMatchWifiNetworkKeys.isEmpty()) { + throw new IllegalArgumentException("Trying to build non wifi match rule: " + + mMatchRule + " with wifi network keys"); + } + } + /** * For backward compatibility, deduce match rule to a wildcard match rule * if the Subscriber Ids are empty. @@ -1133,7 +1179,7 @@ public final class NetworkTemplate implements Parcelable { if (mMatchRule == MATCH_MOBILE && mMatchSubscriberIds.isEmpty()) { return MATCH_MOBILE_WILDCARD; } else if (mMatchRule == MATCH_WIFI && mMatchSubscriberIds.isEmpty() - && mWifiNetworkKey == WIFI_NETWORK_KEY_ALL) { + && mMatchWifiNetworkKeys.isEmpty()) { return MATCH_WIFI_WILDCARD; } return mMatchRule; @@ -1153,8 +1199,8 @@ public final class NetworkTemplate implements Parcelable { return new NetworkTemplate(getWildcardDeducedMatchRule(), mMatchSubscriberIds.isEmpty() ? null : mMatchSubscriberIds.iterator().next(), mMatchSubscriberIds.toArray(new String[0]), - mWifiNetworkKey, mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, - subscriberIdMatchRule); + mMatchWifiNetworkKeys.toArray(new String[0]), mMetered, mRoaming, + mDefaultNetwork, mRatType, mOemManaged, subscriberIdMatchRule); } } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java index fa650617f380..d8feb88f0fe4 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java @@ -29,7 +29,6 @@ import android.media.MediaPlayer; import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.DataUnit; import com.android.server.NetworkManagementSocketTagger; @@ -59,19 +58,19 @@ public class TrafficStats { */ public final static int UNSUPPORTED = -1; - /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */ @Deprecated public static final long KB_IN_BYTES = 1024; - /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */ @Deprecated public static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */ @Deprecated public static final long GB_IN_BYTES = MB_IN_BYTES * 1024; - /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */ @Deprecated public static final long TB_IN_BYTES = GB_IN_BYTES * 1024; - /** @hide @deprecated use {@link DataUnit} instead to clarify SI-vs-IEC */ + /** @hide @deprecated use {@code DataUnit} instead to clarify SI-vs-IEC */ @Deprecated public static final long PB_IN_BYTES = TB_IN_BYTES * 1024; diff --git a/packages/ConnectivityT/service/src/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java index d1e432e80f51..179d9459fd84 100644 --- a/packages/ConnectivityT/service/src/com/android/server/IpSecService.java +++ b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java @@ -1236,37 +1236,53 @@ public class IpSecService extends IIpSecService.Stub { int callingUid = Binder.getCallingUid(); UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid); final int resourceId = mNextResourceId++; - FileDescriptor sockFd = null; + + ParcelFileDescriptor pFd = null; try { if (!userRecord.mSocketQuotaTracker.isAvailable()) { return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } - sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - mUidFdTagger.tag(sockFd, callingUid); + FileDescriptor sockFd = null; + try { + sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + pFd = ParcelFileDescriptor.dup(sockFd); + } finally { + IoUtils.closeQuietly(sockFd); + } + mUidFdTagger.tag(pFd.getFileDescriptor(), callingUid); // This code is common to both the unspecified and specified port cases Os.setsockoptInt( - sockFd, + pFd.getFileDescriptor(), OsConstants.IPPROTO_UDP, OsConstants.UDP_ENCAP, OsConstants.UDP_ENCAP_ESPINUDP); - mNetd.ipSecSetEncapSocketOwner(new ParcelFileDescriptor(sockFd), callingUid); + mNetd.ipSecSetEncapSocketOwner(pFd, callingUid); if (port != 0) { Log.v(TAG, "Binding to port " + port); - Os.bind(sockFd, INADDR_ANY, port); + Os.bind(pFd.getFileDescriptor(), INADDR_ANY, port); } else { - port = bindToRandomPort(sockFd); + port = bindToRandomPort(pFd.getFileDescriptor()); } userRecord.mEncapSocketRecords.put( resourceId, new RefcountedResource<EncapSocketRecord>( - new EncapSocketRecord(resourceId, sockFd, port), binder)); - return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd); + new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port), + binder)); + return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, + pFd.getFileDescriptor()); } catch (IOException | ErrnoException e) { - IoUtils.closeQuietly(sockFd); + try { + if (pFd != null) { + pFd.close(); + } + } catch (IOException ex) { + // Nothing can be done at this point + Log.e(TAG, "Failed to close pFd."); + } } // If we make it to here, then something has gone wrong and we couldn't open a socket. // The only reasonable condition that would cause that is resource unavailable. diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java index e6433db11d7b..bb123a38ebc0 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java @@ -24,19 +24,19 @@ import static android.net.NetworkStats.UID_ALL; import static com.android.server.NetworkManagementSocketTagger.kernelToTag; +import android.annotation.NonNull; import android.annotation.Nullable; import android.net.INetd; import android.net.NetworkStats; import android.net.UnderlyingNetworkInfo; -import android.net.util.NetdService; import android.os.RemoteException; import android.os.StrictMode; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.ProcFileReader; +import com.android.net.module.util.CollectionUtils; import libcore.io.IoUtils; @@ -70,7 +70,7 @@ public class NetworkStatsFactory { private final boolean mUseBpfStats; - private INetd mNetdService; + private final INetd mNetd; /** * Guards persistent data access in this class @@ -158,12 +158,12 @@ public class NetworkStatsFactory { NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces); } - public NetworkStatsFactory() { - this(new File("/proc/"), true); + public NetworkStatsFactory(@NonNull INetd netd) { + this(new File("/proc/"), true, netd); } @VisibleForTesting - public NetworkStatsFactory(File procRoot, boolean useBpfStats) { + public NetworkStatsFactory(File procRoot, boolean useBpfStats, @NonNull INetd netd) { mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all"); mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt"); mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats"); @@ -172,6 +172,7 @@ public class NetworkStatsFactory { mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1); mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1); } + mNetd = netd; } public NetworkStats readBpfNetworkStatsDev() throws IOException { @@ -298,10 +299,7 @@ public class NetworkStatsFactory { // Ask netd to do a active map stats swap. When the binder call successfully returns, // the system server should be able to safely read and clean the inactive map // without race problem. - if (mNetdService == null) { - mNetdService = NetdService.getInstance(); - } - mNetdService.trafficSwapActiveStatsMap(); + mNetd.trafficSwapActiveStatsMap(); } /** @@ -434,7 +432,7 @@ public class NetworkStatsFactory { entry.txBytes = reader.nextLong(); entry.txPackets = reader.nextLong(); - if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface)) + if ((limitIfaces == null || CollectionUtils.contains(limitIfaces, entry.iface)) && (limitUid == UID_ALL || limitUid == entry.uid) && (limitTag == TAG_ALL || limitTag == entry.tag)) { stats.insertEntry(entry); diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java index 1a0866d2f9c5..b57a4f920b60 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java @@ -18,8 +18,6 @@ package com.android.server.net; import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES; -import static com.android.internal.util.Preconditions.checkArgument; - import android.app.usage.NetworkStatsManager; import android.net.DataUsageRequest; import android.net.NetworkIdentitySet; @@ -38,7 +36,7 @@ import android.os.Messenger; import android.os.Process; import android.os.RemoteException; import android.util.ArrayMap; -import android.util.Slog; +import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; @@ -83,7 +81,7 @@ class NetworkStatsObservers { RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid, accessLevel); - if (LOGV) Slog.v(TAG, "Registering observer for " + request); + if (LOGV) Log.v(TAG, "Registering observer for " + request); getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo)); return request; } @@ -116,7 +114,7 @@ class NetworkStatsObservers { if (mHandler == null) { synchronized (this) { if (mHandler == null) { - if (LOGV) Slog.v(TAG, "Creating handler"); + if (LOGV) Log.v(TAG, "Creating handler"); mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback); } } @@ -172,15 +170,15 @@ class NetworkStatsObservers { RequestInfo requestInfo; requestInfo = mDataUsageRequests.get(request.requestId); if (requestInfo == null) { - if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request); + if (LOGV) Log.v(TAG, "Trying to unregister unknown request " + request); return; } if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) { - Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request); + Log.w(TAG, "Caller uid " + callingUid + " is not owner of " + request); return; } - if (LOGV) Slog.v(TAG, "Unregistering " + request); + if (LOGV) Log.v(TAG, "Unregistering " + request); mDataUsageRequests.remove(request.requestId); requestInfo.unlinkDeathRecipient(); requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED); @@ -201,7 +199,7 @@ class NetworkStatsObservers { // Cap the minimum threshold to a safe default to avoid too many callbacks long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes); if (thresholdInBytes < request.thresholdInBytes) { - Slog.w(TAG, "Threshold was too low for " + request + Log.w(TAG, "Threshold was too low for " + request + ". Overriding to a safer default of " + thresholdInBytes + " bytes"); } return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(), @@ -216,7 +214,10 @@ class NetworkStatsObservers { accessLevel); } else { // Safety check in case a new access level is added and we forgot to update this - checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY); + if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) { + throw new IllegalArgumentException( + "accessLevel " + accessLevel + " is less than DEVICESUMMARY."); + } return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid, accessLevel); } @@ -255,8 +256,9 @@ class NetworkStatsObservers { @Override public void binderDied() { - if (LOGV) Slog.v(TAG, "RequestInfo binderDied(" - + mRequest + ", " + mBinder + ")"); + if (LOGV) { + Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mBinder + ")"); + } mStatsObserver.unregister(mRequest, Process.SYSTEM_UID); callCallback(NetworkStatsManager.CALLBACK_RELEASED); } @@ -299,13 +301,13 @@ class NetworkStatsObservers { msg.setData(bundle); try { if (LOGV) { - Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType) + Log.v(TAG, "sending notification " + callbackTypeToName(callbackType) + " for " + mRequest); } mMessenger.send(msg); } catch (RemoteException e) { // May occur naturally in the race of binder death. - Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest); + Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest); } } @@ -341,7 +343,7 @@ class NetworkStatsObservers { protected boolean checkStats() { long bytesSoFar = getTotalBytesForNetwork(mRequest.template); if (LOGV) { - Slog.v(TAG, bytesSoFar + " bytes so far since notification for " + Log.v(TAG, bytesSoFar + " bytes so far since notification for " + mRequest.template); } if (bytesSoFar > mRequest.thresholdInBytes) { @@ -416,7 +418,7 @@ class NetworkStatsObservers { return history.getTotalBytes(); } catch (SecurityException e) { if (LOGV) { - Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid " + Log.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid " + uid); } return 0; diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java index 5e27c776ccb0..c371f0859aa1 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java @@ -32,15 +32,12 @@ import android.net.TrafficStats; import android.os.Binder; import android.os.DropBoxManager; import android.service.NetworkStatsRecorderProto; +import android.util.IndentingPrintWriter; import android.util.Log; -import android.util.MathUtils; -import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.FileRotator; -import com.android.internal.util.IndentingPrintWriter; - -import com.google.android.collect.Sets; +import com.android.net.module.util.NetworkStatsUtils; import libcore.io.IoUtils; @@ -132,8 +129,8 @@ public class NetworkStatsRecorder { } public void setPersistThreshold(long thresholdBytes) { - if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes); - mPersistThresholdBytes = MathUtils.constrain( + if (LOGV) Log.v(TAG, "setPersistThreshold() with " + thresholdBytes); + mPersistThresholdBytes = NetworkStatsUtils.constrain( thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES); } @@ -185,7 +182,7 @@ public class NetworkStatsRecorder { } private NetworkStatsCollection loadLocked(long start, long end) { - if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie); + if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie); final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration); try { mRotator.readMatching(res, start, end); @@ -207,7 +204,7 @@ public class NetworkStatsRecorder { */ public void recordSnapshotLocked(NetworkStats snapshot, Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) { - final HashSet<String> unknownIfaces = Sets.newHashSet(); + final HashSet<String> unknownIfaces = new HashSet<>(); // skip recording when snapshot missing if (snapshot == null) return; @@ -272,7 +269,7 @@ public class NetworkStatsRecorder { mLastSnapshot = snapshot; if (LOGV && unknownIfaces.size() > 0) { - Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); + Log.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); } } @@ -296,7 +293,7 @@ public class NetworkStatsRecorder { public void forcePersistLocked(long currentTimeMillis) { Objects.requireNonNull(mRotator, "missing FileRotator"); if (mPending.isDirty()) { - if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie); + if (LOGD) Log.d(TAG, "forcePersistLocked() writing for " + mCookie); try { mRotator.rewriteActive(mPendingRewriter, currentTimeMillis); mRotator.maybeRotate(currentTimeMillis); diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index df066c21d7be..ced2e22f149d 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -25,7 +25,6 @@ import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkIdentity.SUBTYPE_COMBINED; -import static android.net.NetworkStack.checkNetworkStackPermission; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.IFACE_VT; @@ -43,7 +42,6 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileWildcard; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; -import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UNSUPPORTED; @@ -90,6 +88,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.net.DataUsageRequest; +import android.net.INetd; import android.net.INetworkManagementEventObserver; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; @@ -107,13 +106,13 @@ import android.net.NetworkStatsCollection; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; +import android.net.TetheringManager; import android.net.TrafficStats; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.netstats.provider.INetworkStatsProvider; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.net.netstats.provider.NetworkStatsProvider; -import android.os.BestClock; import android.os.Binder; import android.os.DropBoxManager; import android.os.Environment; @@ -141,28 +140,30 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; +import android.util.IndentingPrintWriter; import android.util.Log; -import android.util.MathUtils; -import android.util.Slog; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.DumpUtils; import com.android.internal.util.FileRotator; -import com.android.internal.util.IndentingPrintWriter; +import com.android.net.module.util.BestClock; import com.android.net.module.util.BinderUtils; +import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.NetworkStatsUtils; +import com.android.net.module.util.PermissionUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.time.Clock; import java.time.ZoneOffset; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -410,10 +411,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - + final INetd netd = INetd.Stub.asInterface( + (IBinder) context.getSystemService(Context.NETD_SERVICE)); final NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager, wakeLock, getDefaultClock(), - new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(), + new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd), new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(), new Dependencies()); service.registerLocalService(); @@ -529,8 +531,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // watch for tethering changes - final IntentFilter tetherFilter = new IntentFilter(ACTION_TETHER_STATE_CHANGED); - mContext.registerReceiver(mTetherReceiver, tetherFilter, null, mHandler); + final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class); + tetheringManager.registerTetheringEventCallback( + new HandlerExecutor(mHandler), mTetherListener); // listen for periodic polling events final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL); @@ -586,7 +589,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @GuardedBy("mStatsLock") private void shutdownLocked() { - mContext.unregisterReceiver(mTetherReceiver); + final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class); + tetheringManager.unregisterTetheringEventCallback(mTetherListener); mContext.unregisterReceiver(mPollReceiver); mContext.unregisterReceiver(mRemovedReceiver); mContext.unregisterReceiver(mUserReceiver); @@ -646,7 +650,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { try { mNetworkManager.setGlobalAlert(mGlobalAlertBytes); } catch (IllegalStateException e) { - Slog.w(TAG, "problem registering for global alert: " + e); + Log.w(TAG, "problem registering for global alert: " + e); } catch (RemoteException e) { // ignored; service lives in system_server } @@ -764,7 +768,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return stats; } catch (NullPointerException e) { // TODO: Track down and fix the cause of this crash and remove this catch block. - Slog.wtf(TAG, "NullPointerException in getSummaryForAllUid", e); + Log.wtf(TAG, "NullPointerException in getSummaryForAllUid", e); throw e; } } @@ -809,7 +813,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) { return NetworkStatsAccess.checkAccessLevel( - mContext, Binder.getCallingUid(), callingPackage); + mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage); } /** @@ -821,7 +825,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { SubscriptionPlan plan = null; if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0 && mSettings.getAugmentEnabled()) { - if (LOGD) Slog.d(TAG, "Resolving plan for " + template); + if (LOGD) Log.d(TAG, "Resolving plan for " + template); final long token = Binder.clearCallingIdentity(); try { plan = LocalServices.getService(NetworkPolicyManagerInternal.class) @@ -829,7 +833,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } finally { Binder.restoreCallingIdentity(token); } - if (LOGD) Slog.d(TAG, "Resolved to plan " + plan); + if (LOGD) Log.d(TAG, "Resolved to plan " + plan); } return plan; } @@ -932,7 +936,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public String[] getMobileIfaces() { // TODO (b/192758557): Remove debug log. - if (ArrayUtils.contains(mMobileIfaces, null)) { + if (CollectionUtils.contains(mMobileIfaces, null)) { throw new NullPointerException( "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces)); } @@ -981,7 +985,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @NonNull NetworkStateSnapshot[] networkStates, @Nullable String activeIface, @NonNull UnderlyingNetworkInfo[] underlyingNetworkInfos) { - checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); final long token = Binder.clearCallingIdentity(); try { @@ -1010,9 +1014,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void advisePersistThreshold(long thresholdBytes) { // clamp threshold into safe range - mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES); + mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes, + 128 * KB_IN_BYTES, 2 * MB_IN_BYTES); if (LOGV) { - Slog.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to " + Log.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to " + mPersistThreshold); } @@ -1149,14 +1154,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Receiver that watches for {@link Tethering} to claim interface pairs. + * Listener that watches for {@link TetheringManager} to claim interface pairs. */ - private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - performPoll(FLAG_PERSIST_NETWORK); - } - }; + private final TetheringManager.TetheringEventCallback mTetherListener = + new TetheringManager.TetheringEventCallback() { + @Override + public void onUpstreamChanged(@Nullable Network network) { + performPoll(FLAG_PERSIST_NETWORK); + } + }; private BroadcastReceiver mPollReceiver = new BroadcastReceiver() { @Override @@ -1196,13 +1202,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // On background handler thread, and USER_REMOVED is protected // broadcast. - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userId == -1) return; + final UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); + if (userHandle == null) return; synchronized (mStatsLock) { mWakeLock.acquire(); try { - removeUserLocked(userId); + removeUserLocked(userHandle); } finally { mWakeLock.release(); } @@ -1227,7 +1233,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void limitReached(String limitName, String iface) { // only someone like NMS should be calling us - NetworkStack.checkNetworkStackPermission(mContext); + PermissionUtils.enforceNetworkStackPermission(mContext); if (LIMIT_GLOBAL_ALERT.equals(limitName)) { // kick off background poll to collect network stats unless there is already @@ -1275,7 +1281,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void handleNotifyNetworkStatusLocked(@NonNull Network[] defaultNetworks, @NonNull NetworkStateSnapshot[] snapshots) { if (!mSystemReady) return; - if (LOGV) Slog.v(TAG, "handleNotifyNetworkStatusLocked()"); + if (LOGV) Log.v(TAG, "handleNotifyNetworkStatusLocked()"); // take one last stats snapshot before updating iface mapping. this // isn't perfect, since the kernel may already be counting traffic from @@ -1299,7 +1305,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final int displayTransport = getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes()); final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport); - final boolean isDefault = ArrayUtils.contains(mDefaultNetworks, snapshot.getNetwork()); + final boolean isDefault = CollectionUtils.contains( + mDefaultNetworks, snapshot.getNetwork()); final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED : getSubTypeForStateSnapshot(snapshot); final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, @@ -1382,7 +1389,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mMobileIfaces = mobileIfaces.toArray(new String[0]); // TODO (b/192758557): Remove debug log. - if (ArrayUtils.contains(mMobileIfaces, null)) { + if (CollectionUtils.contains(mMobileIfaces, null)) { throw new NullPointerException( "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces)); } @@ -1397,7 +1404,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (spec instanceof TelephonyNetworkSpecifier) { return ((TelephonyNetworkSpecifier) spec).getSubscriptionId(); } else { - Slog.wtf(TAG, "getSubIdForState invalid NetworkSpecifier"); + Log.wtf(TAG, "getSubIdForState invalid NetworkSpecifier"); return INVALID_SUBSCRIPTION_ID; } } @@ -1481,7 +1488,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { try { recordSnapshotLocked(currentTime); } catch (IllegalStateException e) { - Slog.w(TAG, "problem reading network stats: " + e); + Log.w(TAG, "problem reading network stats: " + e); } catch (RemoteException e) { // ignored; service lives in system_server } @@ -1506,7 +1513,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @GuardedBy("mStatsLock") private void performPollLocked(int flags) { if (!mSystemReady) return; - if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")"); + if (LOGV) Log.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")"); Trace.traceBegin(TRACE_TAG_NETWORK, "performPollLocked"); final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0; @@ -1628,7 +1635,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ @GuardedBy("mStatsLock") private void removeUidsLocked(int... uids) { - if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids)); + if (LOGV) Log.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids)); // Perform one last poll before removing performPollLocked(FLAG_PERSIST_ALL); @@ -1646,20 +1653,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * Clean up {@link #mUidRecorder} after user is removed. */ @GuardedBy("mStatsLock") - private void removeUserLocked(int userId) { - if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId); + private void removeUserLocked(@NonNull UserHandle userHandle) { + if (LOGV) Log.v(TAG, "removeUserLocked() for UserHandle=" + userHandle); // Build list of UIDs that we should clean up - int[] uids = new int[0]; + final ArrayList<Integer> uids = new ArrayList<>(); final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications( PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS); for (ApplicationInfo app : apps) { - final int uid = UserHandle.getUid(userId, app.uid); - uids = ArrayUtils.appendInt(uids, uid); + final int uid = userHandle.getUid(app.uid); + uids.add(uid); } - removeUidsLocked(uids); + removeUidsLocked(CollectionUtils.toIntArray(uids)); } private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal { @@ -1702,7 +1709,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public void setStatsProviderWarningAndLimitAsync( @NonNull String iface, long warning, long limit) { if (LOGV) { - Slog.v(TAG, "setStatsProviderWarningAndLimitAsync(" + Log.v(TAG, "setStatsProviderWarningAndLimitAsync(" + iface + "," + warning + "," + limit + ")"); } invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, @@ -1712,7 +1719,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, rawWriter)) return; + if (!PermissionUtils.checkDumpPermission(mContext, TAG, rawWriter)) return; long duration = DateUtils.DAY_IN_MILLIS; final HashSet<String> argSet = new HashSet<String>(); @@ -1773,15 +1780,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { pw.println("Configs:"); pw.increaseIndent(); - pw.printPair(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled()); + pw.print(NETSTATS_COMBINE_SUBTYPE_ENABLED, mSettings.getCombineSubtypeEnabled()); pw.println(); pw.decreaseIndent(); pw.println("Active interfaces:"); pw.increaseIndent(); for (int i = 0; i < mActiveIfaces.size(); i++) { - pw.printPair("iface", mActiveIfaces.keyAt(i)); - pw.printPair("ident", mActiveIfaces.valueAt(i)); + pw.print("iface", mActiveIfaces.keyAt(i)); + pw.print("ident", mActiveIfaces.valueAt(i)); pw.println(); } pw.decreaseIndent(); @@ -1789,8 +1796,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { pw.println("Active UID interfaces:"); pw.increaseIndent(); for (int i = 0; i < mActiveUidIfaces.size(); i++) { - pw.printPair("iface", mActiveUidIfaces.keyAt(i)); - pw.printPair("ident", mActiveUidIfaces.valueAt(i)); + pw.print("iface", mActiveUidIfaces.keyAt(i)); + pw.print("ident", mActiveUidIfaces.valueAt(i)); pw.println(); } pw.decreaseIndent(); @@ -1863,7 +1870,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @GuardedBy("mStatsLock") private void dumpProtoLocked(FileDescriptor fd) { - final ProtoOutputStream proto = new ProtoOutputStream(fd); + final ProtoOutputStream proto = new ProtoOutputStream(new FileOutputStream(fd)); // TODO Right now it writes all history. Should it limit to the "since-boot" log? diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java index 5646c752fc90..9bb7bb80782b 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java @@ -33,7 +33,7 @@ import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.CollectionUtils; +import com.android.net.module.util.CollectionUtils; import java.util.ArrayList; import java.util.List; @@ -99,18 +99,20 @@ public class NetworkStatsSubscriptionsMonitor extends // prevent binder call to telephony when querying RAT. Keep listener registration with empty // IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported // with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration. - final List<Pair<Integer, String>> filteredNewSubs = - CollectionUtils.mapNotNull(newSubs, subId -> { - final String subscriberId = mTeleManager.getSubscriberId(subId); - return TextUtils.isEmpty(subscriberId) ? null : new Pair(subId, subscriberId); - }); + final List<Pair<Integer, String>> filteredNewSubs = new ArrayList<>(); + for (final int subId : newSubs) { + final String subscriberId = + mTeleManager.createForSubscriptionId(subId).getSubscriberId(); + if (!TextUtils.isEmpty(subscriberId)) { + filteredNewSubs.add(new Pair(subId, subscriberId)); + } + } for (final Pair<Integer, String> sub : filteredNewSubs) { // Fully match listener with subId and IMSI, since in some rare cases, IMSI might be // suddenly change regardless of subId, such as switch IMSI feature in modem side. // If that happens, register new listener with new IMSI and remove old one later. - if (CollectionUtils.find(mRatListeners, - it -> it.equalsKey(sub.first, sub.second)) != null) { + if (CollectionUtils.any(mRatListeners, it -> it.equalsKey(sub.first, sub.second))) { continue; } @@ -126,8 +128,8 @@ public class NetworkStatsSubscriptionsMonitor extends for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) { // If there is no subId and IMSI matched the listener, removes it. - if (CollectionUtils.find(filteredNewSubs, - it -> listener.equalsKey(it.first, it.second)) == null) { + if (!CollectionUtils.any(filteredNewSubs, + it -> listener.equalsKey(it.first, it.second))) { handleRemoveRatTypeListener(listener); } } @@ -148,9 +150,10 @@ public class NetworkStatsSubscriptionsMonitor extends * @return collapsed RatType for the given subscriberId */ public int getRatTypeForSubscriberId(@NonNull String subscriberId) { - final RatTypeListener match = CollectionUtils.find(mRatListeners, + final int index = CollectionUtils.indexOf(mRatListeners, it -> TextUtils.equals(subscriberId, it.mSubscriberId)); - return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN; + return index != -1 ? mRatListeners.get(index).mLastCollapsedRatType + : TelephonyManager.NETWORK_TYPE_UNKNOWN; } /** diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java index 7e0490a233f9..063d78986ae5 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java @@ -264,7 +264,8 @@ public class PackageInstallerImpl { String action = ACTION_INSTALL_COMMIT + "." + packageName; IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(action); - mContext.registerReceiver(broadcastReceiver, intentFilter); + mContext.registerReceiver(broadcastReceiver, intentFilter, + Context.RECEIVER_EXPORTED_UNAUDITED); // Create a matching PendingIntent and use it to generate the IntentSender Intent broadcastIntent = new Intent(action); diff --git a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml index e586dbb7770c..b1276303b801 100644 --- a/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml +++ b/packages/SettingsLib/FooterPreference/res/layout-v31/preference_footer.xml @@ -24,7 +24,6 @@ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:background="?android:attr/selectableItemBackground" android:orientation="vertical" - android:importantForAccessibility = "no" android:clipToPadding="false"> <LinearLayout diff --git a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml index 990860ad3291..23192b6ce138 100644 --- a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml +++ b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml @@ -23,7 +23,6 @@ android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:background="?android:attr/selectableItemBackground" - android:importantForAccessibility = "no" android:clipToPadding="false"> <LinearLayout diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java index fe7988f7e500..e51bb45ebca2 100644 --- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java @@ -61,6 +61,7 @@ public class FooterPreference extends Preference { title.setMovementMethod(new LinkMovementMethod()); title.setClickable(false); title.setLongClickable(false); + title.setFocusable(false); if (!TextUtils.isEmpty(mContentDescription)) { title.setContentDescription(mContentDescription); } @@ -79,6 +80,7 @@ public class FooterPreference extends Preference { if (!TextUtils.isEmpty(mLearnMoreContentDescription)) { learnMore.setContentDescription(mLearnMoreContentDescription); } + learnMore.setFocusable(false); } else { learnMore.setVisibility(View.GONE); } diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index ab7b54d98285..6b9b75011f5b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -17,9 +17,11 @@ package com.android.settingslib.dream; import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -57,6 +59,8 @@ public class DreamBackend { public boolean isActive; public ComponentName componentName; public ComponentName settingsComponentName; + public CharSequence description; + public Drawable previewImage; @Override public String toString() { @@ -118,23 +122,47 @@ public class DreamBackend { PackageManager.GET_META_DATA); List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size()); for (ResolveInfo resolveInfo : resolveInfos) { - if (resolveInfo.serviceInfo == null) + if (resolveInfo.serviceInfo == null) { continue; + } DreamInfo dreamInfo = new DreamInfo(); dreamInfo.caption = resolveInfo.loadLabel(pm); dreamInfo.icon = resolveInfo.loadIcon(pm); + dreamInfo.description = getDescription(resolveInfo, pm); dreamInfo.componentName = getDreamComponentName(resolveInfo); dreamInfo.isActive = dreamInfo.componentName.equals(activeDream); - dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo); + + final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo); + if (dreamMetadata != null) { + dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity; + dreamInfo.previewImage = dreamMetadata.mPreviewImage; + } + dreamInfos.add(dreamInfo); } Collections.sort(dreamInfos, mComparator); return dreamInfos; } + private static CharSequence getDescription(ResolveInfo resolveInfo, PackageManager pm) { + String packageName = resolveInfo.resolvePackageName; + ApplicationInfo applicationInfo = null; + if (packageName == null) { + packageName = resolveInfo.serviceInfo.packageName; + applicationInfo = resolveInfo.serviceInfo.applicationInfo; + } + if (resolveInfo.serviceInfo.descriptionRes != 0) { + return pm.getText(packageName, + resolveInfo.serviceInfo.descriptionRes, + applicationInfo); + } + return null; + } + public ComponentName getDefaultDream() { - if (mDreamManager == null) + if (mDreamManager == null) { return null; + } try { return mDreamManager.getDefaultDreamComponentForUser(mContext.getUserId()); } catch (RemoteException e) { @@ -306,57 +334,77 @@ public class DreamBackend { } private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) { - if (resolveInfo == null || resolveInfo.serviceInfo == null) + if (resolveInfo == null || resolveInfo.serviceInfo == null) { return null; + } return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); } - private static ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) { - if (resolveInfo == null - || resolveInfo.serviceInfo == null - || resolveInfo.serviceInfo.metaData == null) + private static final class DreamMetadata { + @Nullable + Drawable mPreviewImage; + @Nullable + ComponentName mSettingsActivity; + } + + @Nullable + private static TypedArray readMetadata(PackageManager pm, ServiceInfo serviceInfo) { + if (serviceInfo == null || serviceInfo.metaData == null) { return null; - String cn = null; - XmlResourceParser parser = null; - Exception caughtException = null; - try { - parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA); + } + try (XmlResourceParser parser = + serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA)) { if (parser == null) { Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data"); return null; } - Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo); + Resources res = pm.getResourcesForApplication(serviceInfo.applicationInfo); AttributeSet attrs = Xml.asAttributeSet(parser); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && type != XmlPullParser.START_TAG) { + while (true) { + final int type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT || type == XmlPullParser.START_TAG) { + break; + } } String nodeName = parser.getName(); if (!"dream".equals(nodeName)) { Log.w(TAG, "Meta-data does not start with dream tag"); return null; } - TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream); - cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity); - sa.recycle(); - } catch (PackageManager.NameNotFoundException|IOException|XmlPullParserException e) { - caughtException = e; - } finally { - if (parser != null) parser.close(); - } - if (caughtException != null) { - Log.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException); + return res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream); + } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) { + Log.w(TAG, "Error parsing : " + serviceInfo.packageName, e); return null; } - if (cn != null && cn.indexOf('/') < 0) { - cn = resolveInfo.serviceInfo.packageName + "/" + cn; + } + + private static ComponentName convertToComponentName(String flattenedString, + ServiceInfo serviceInfo) { + if (flattenedString == null) return null; + + if (flattenedString.indexOf('/') < 0) { + flattenedString = serviceInfo.packageName + "/" + flattenedString; } - return cn == null ? null : ComponentName.unflattenFromString(cn); + return ComponentName.unflattenFromString(flattenedString); + } + + private static DreamMetadata getDreamMetadata(PackageManager pm, ResolveInfo resolveInfo) { + DreamMetadata result = new DreamMetadata(); + if (resolveInfo == null) return result; + TypedArray rawMetadata = readMetadata(pm, resolveInfo.serviceInfo); + if (rawMetadata == null) return result; + result.mSettingsActivity = convertToComponentName(rawMetadata.getString( + com.android.internal.R.styleable.Dream_settingsActivity), resolveInfo.serviceInfo); + result.mPreviewImage = rawMetadata.getDrawable( + com.android.internal.R.styleable.Dream_previewImage); + rawMetadata.recycle(); + return result; } private static void logd(String msg, Object... args) { - if (DEBUG) + if (DEBUG) { Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); + } } private static class DreamInfoComparator implements Comparator<DreamInfo> { diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java index 39e6dcecba31..5860bdabe764 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java @@ -27,6 +27,7 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.DirectBootAwareness; import java.util.ArrayList; import java.util.HashMap; @@ -88,7 +89,8 @@ public class InputMethodSettingValuesWrapper { public void refreshAllInputMethodAndSubtypes() { mMethodList.clear(); - mMethodList.addAll(mImm.getInputMethodListAsUser(mContentResolver.getUserId())); + mMethodList.addAll(mImm.getInputMethodListAsUser( + mContentResolver.getUserId(), DirectBootAwareness.ANY)); } public List<InputMethodInfo> getInputMethodList() { diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java index 24233729ad45..21a4ac6220fb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtils.java @@ -29,7 +29,7 @@ public class WifiEnterpriseRestrictionUtils { private static final String TAG = "WifiEntResUtils"; /** - * Confirm Wi-Fi tethering is available according to whether user restriction is set + * Confirm Wi-Fi tethering is allowed according to whether user restriction is set * * @param context A context * @return whether the device is permitted to use Wi-Fi Tethering @@ -44,6 +44,22 @@ public class WifiEnterpriseRestrictionUtils { return true; } + /** + * Confirm Wi-Fi Direct is allowed according to whether user restriction is set + * + * @param context A context + * @return whether the device is permitted to use Wi-Fi Direct + */ + public static boolean isWifiDirectAllowed(Context context) { + final UserManager userManager = context.getSystemService(UserManager.class); + final Bundle restrictions = userManager.getUserRestrictions(); + if (isAtLeastT() && restrictions.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)) { + Log.i(TAG, "Wi-Fi Direct isn't available due to user restriction."); + return false; + } + return true; + } + @ChecksSdkIntAtLeast(api=Build.VERSION_CODES.TIRAMISU) private static boolean isAtLeastT() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java new file mode 100644 index 000000000000..7ffae4094add --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java @@ -0,0 +1,131 @@ +/* + * 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.settingslib.wifi; + +import static android.os.UserManager.DISALLOW_CONFIG_WIFI; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Bundle; +import android.os.UserManager; +import android.util.SparseArray; + +import androidx.annotation.VisibleForTesting; + +import java.util.HashMap; +import java.util.Map; + +/** + * This is a singleton class for Wi-Fi restrictions caching. + */ +public class WifiRestrictionsCache { + private static final String TAG = "WifiResCache"; + + /** + * Manages mapping between user ID and corresponding singleton {@link WifiRestrictionsCache} + * object. + */ + @VisibleForTesting + protected static final SparseArray<WifiRestrictionsCache> sInstances = new SparseArray<>(); + + @VisibleForTesting + protected UserManager mUserManager; + @VisibleForTesting + protected Bundle mUserRestrictions; + @VisibleForTesting + protected final Map<String, Boolean> mRestrictions = new HashMap<>(); + + /** + * @return an instance of {@link WifiRestrictionsCache} object. + */ + @NonNull + public static WifiRestrictionsCache getInstance(@NonNull Context context) { + final int requestUserId = context.getUserId(); + WifiRestrictionsCache cache; + synchronized (sInstances) { + // We have same user context as request. + if (sInstances.indexOfKey(requestUserId) >= 0) { + return sInstances.get(requestUserId); + } + // Request by a new user context. + cache = new WifiRestrictionsCache(context); + sInstances.put(context.getUserId(), cache); + } + return cache; + } + + /** + * Removes all the instances. + */ + public static void clearInstance() { + synchronized (sInstances) { + for (int i = 0; i < sInstances.size(); i++) { + int key = sInstances.keyAt(i); + WifiRestrictionsCache cache = sInstances.get(key); + cache.clearRestrictions(); + sInstances.remove(key); + } + sInstances.clear(); + } + } + + /** + * Constructor to create a singleton class for Wi-Fi restrictions cache. + * + * @param context The Context this is associated with. + */ + protected WifiRestrictionsCache(@NonNull Context context) { + mUserManager = context.getSystemService(UserManager.class); + if (mUserManager != null) { + mUserRestrictions = mUserManager.getUserRestrictions(); + } + } + + /** + * @return the boolean value of the restrictions + */ + public Boolean getRestriction(String key) { + if (mUserRestrictions == null) { + return false; + } + Boolean restriction; + synchronized (mRestrictions) { + if (mRestrictions.containsKey(key)) { + return mRestrictions.get(key); + } + restriction = mUserRestrictions.getBoolean(key); + mRestrictions.put(key, restriction); + } + return restriction; + } + + /** + * Removes all the restrictions. + */ + public void clearRestrictions() { + synchronized (mRestrictions) { + mRestrictions.clear(); + } + } + + /** + * @return Whether the user is allowed to config Wi-Fi. + */ + public Boolean isConfigWifiAllowed() { + return !getRestriction(DISALLOW_CONFIG_WIFI); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index bf5ab1c9951a..426ea42eb2c5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -342,7 +342,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro resumeScanning(); if (!mRegistered) { - mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler); + mContext.registerReceiver(mReceiver, mFilter, null /* permission */, mWorkHandler, + Context.RECEIVER_EXPORTED_UNAUDITED); // NetworkCallback objects cannot be reused. http://b/20701525 . mNetworkCallback = new WifiTrackerNetworkCallback(); mConnectivityManager.registerNetworkCallback( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java index 455dc64fa97d..3c339defbaf4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEnterpriseRestrictionUtilsTest.java @@ -77,4 +77,30 @@ public class WifiEnterpriseRestrictionUtilsTest { assertThat(WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(mContext)).isTrue(); } + + @Test + public void isWifiDirectAllowed_setSDKForS_shouldReturnTrue() { + ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.S); + when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)).thenReturn(true); + + assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isTrue(); + } + + @Test + public void isWifiDirectAllowed_setSDKForTAndDisallowForRestriction_shouldReturnFalse() { + ReflectionHelpers.setStaticField( + Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); + when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)).thenReturn(true); + + assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isFalse(); + } + + @Test + public void isWifiDirectAllowed_setSDKForTAndAllowForRestriction_shouldReturnTrue() { + ReflectionHelpers.setStaticField( + Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.TIRAMISU); + when(mBundle.getBoolean(UserManager.DISALLOW_WIFI_DIRECT)).thenReturn(false); + + assertThat(WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(mContext)).isTrue(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiRestrictionsCacheTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiRestrictionsCacheTest.java new file mode 100644 index 000000000000..404e0e88c34f --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiRestrictionsCacheTest.java @@ -0,0 +1,172 @@ +/* + * 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.settingslib.wifi; + +import static android.os.UserManager.DISALLOW_CONFIG_WIFI; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Bundle; +import android.os.UserManager; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class WifiRestrictionsCacheTest { + + private static final int USER_OWNER = 0; + private static final int USER_1 = 1; + private static final int USER_2 = 2; + private static final int USER_3 = 3; + private static final int USER_GUEST = 10; + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock + UserManager mUserManager; + @Mock + Bundle mUserRestrictionsOwner; + @Mock + Bundle mUserRestrictionsGuest; + + private Context mContext; + private WifiRestrictionsCache mWifiRestrictionsCacheOwner; + private WifiRestrictionsCache mWifiRestrictionsCacheGuest; + + @Before + public void setUp() { + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + + when(mContext.getUserId()).thenReturn(USER_OWNER); + when(mUserManager.getUserRestrictions()).thenReturn(mUserRestrictionsOwner); + when(mUserRestrictionsOwner.getBoolean(anyString())).thenReturn(false); + mWifiRestrictionsCacheOwner = WifiRestrictionsCache.getInstance(mContext); + + when(mContext.getUserId()).thenReturn(USER_GUEST); + when(mUserManager.getUserRestrictions()).thenReturn(mUserRestrictionsGuest); + when(mUserRestrictionsGuest.getBoolean(anyString())).thenReturn(true); + mWifiRestrictionsCacheGuest = WifiRestrictionsCache.getInstance(mContext); + } + + @After + public void tearDown() { + WifiRestrictionsCache.clearInstance(); + } + + @Test + public void getInstance_sameUserId_sameInstance() { + when(mContext.getUserId()).thenReturn(USER_OWNER); + WifiRestrictionsCache instance1 = WifiRestrictionsCache.getInstance(mContext); + + WifiRestrictionsCache instance2 = WifiRestrictionsCache.getInstance(mContext); + + assertThat(instance1).isEqualTo(instance2); + } + + @Test + public void getInstance_diffUserId_diffInstance() { + when(mContext.getUserId()).thenReturn(USER_OWNER); + WifiRestrictionsCache instance1 = WifiRestrictionsCache.getInstance(mContext); + + when(mContext.getUserId()).thenReturn(USER_GUEST); + WifiRestrictionsCache instance2 = WifiRestrictionsCache.getInstance(mContext); + + assertThat(instance1).isNotEqualTo(instance2); + } + + @Test + public void clearInstance_instanceShouldBeEmpty() { + WifiRestrictionsCache.clearInstance(); + + assertThat(WifiRestrictionsCache.sInstances.size()).isEqualTo(0); + } + + @Test + public void getRestriction_firstTime_getFromSystem() { + Bundle userRestrictions = mock(Bundle.class); + WifiRestrictionsCache wifiRestrictionsCache = mockInstance(USER_1, userRestrictions); + + wifiRestrictionsCache.getRestriction(DISALLOW_CONFIG_WIFI); + + verify(userRestrictions).getBoolean(DISALLOW_CONFIG_WIFI); + } + + @Test + public void getRestriction_secondTime_notGetFromSystem() { + Bundle userRestrictions = mock(Bundle.class); + WifiRestrictionsCache wifiRestrictionsCache = mockInstance(USER_2, userRestrictions); + // First time to get the restriction value + wifiRestrictionsCache.getRestriction(DISALLOW_CONFIG_WIFI); + reset(userRestrictions); + + // Second time to get the restriction value + wifiRestrictionsCache.getRestriction(DISALLOW_CONFIG_WIFI); + + verify(userRestrictions, never()).getBoolean(DISALLOW_CONFIG_WIFI); + } + + @Test + public void clearRestrictions_shouldGetRestrictionFromSystemAgain() { + Bundle userRestrictions = mock(Bundle.class); + WifiRestrictionsCache wifiRestrictionsCache = mockInstance(USER_3, userRestrictions); + // First time to get the restriction value + wifiRestrictionsCache.getRestriction(DISALLOW_CONFIG_WIFI); + reset(userRestrictions); + + // Clear the cache and then second time to get the restriction value + wifiRestrictionsCache.clearRestrictions(); + wifiRestrictionsCache.getRestriction(DISALLOW_CONFIG_WIFI); + + verify(userRestrictions).getBoolean(DISALLOW_CONFIG_WIFI); + } + + @Test + public void isConfigWifiAllowed_ownerUser_returnTrue() { + assertThat(mWifiRestrictionsCacheOwner.isConfigWifiAllowed()).isTrue(); + } + + @Test + public void isConfigWifiAllowed_guestUser_returnFalse() { + assertThat(mWifiRestrictionsCacheGuest.isConfigWifiAllowed()).isFalse(); + } + + private WifiRestrictionsCache mockInstance(int userId, Bundle userRestrictions) { + when(mContext.getUserId()).thenReturn(userId); + when(mUserManager.getUserRestrictions()).thenReturn(userRestrictions); + return WifiRestrictionsCache.getInstance(mContext); + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 4e2111c7d864..16cece93f2de 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -188,6 +188,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, + Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, Settings.Secure.NOTIFICATION_BUBBLES, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index dd1cb6b32d4f..688c48ddebb1 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -321,5 +321,7 @@ public class SecureSettingsValidators { } return true; }); + VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 38a258f6ecf0..d4f9f91b6db4 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -244,9 +244,6 @@ class SettingsProtoDumpUtil { final long autofillToken = p.start(GlobalSettingsProto.AUTOFILL); dumpSetting(s, p, - Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES, - GlobalSettingsProto.Autofill.COMPAT_MODE_ALLOWED_PACKAGES); - dumpSetting(s, p, Settings.Global.AUTOFILL_LOGGING_LEVEL, GlobalSettingsProto.Autofill.LOGGING_LEVEL); dumpSetting(s, p, @@ -1813,6 +1810,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, SecureSettingsProto.Accessibility.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED); + dumpSetting(s, p, + Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, + SecureSettingsProto.Accessibility.ODI_CAPTIONS_VOLUME_UI_ENABLED); p.end(accessibilityToken); final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index ed813a0b1f46..a3f39959e7cf 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -134,7 +134,6 @@ public class SettingsBackupTest { Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE, Settings.Global.ASSISTED_GPS_ENABLED, Settings.Global.AUDIO_SAFE_VOLUME_STATE, - Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES, Settings.Global.AUTOFILL_LOGGING_LEVEL, Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, @@ -657,6 +656,7 @@ public class SettingsBackupTest { Settings.Global.Wearable.WRIST_ORIENTATION_MODE, Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE, Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY, + Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED, Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 559c31a92004..b19ef3a243aa 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -234,15 +234,23 @@ android_library { plugins: ["dagger2-compiler"], } -soong_config_module_type_import { - from: "frameworks/base/services/Android.bp", - module_types: ["system_optimized_java_defaults"], +// Opt-in config for optimizing the SystemUI target using R8. +// Enabled via `export SYSTEMUI_OPTIMIZE_JAVA=true`, or explicitly in Make via +// the `SOONG_CONFIG_ANDROID_SYSTEMUI_OPTIMIZE_JAVA` variable. +// TODO(b/203472868): Enable optimizations by default after stabilizing and +// building out retrace infrastructure. +soong_config_module_type { + name: "systemui_optimized_java_defaults", + module_type: "java_defaults", + config_namespace: "ANDROID", + bool_variables: ["SYSTEMUI_OPTIMIZE_JAVA"], + properties: ["optimize"], } -system_optimized_java_defaults { +systemui_optimized_java_defaults { name: "SystemUI_app_defaults", soong_config_variables: { - SYSTEM_OPTIMIZE_JAVA: { + SYSTEMUI_OPTIMIZE_JAVA: { optimize: { enabled: true, optimize: true, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1e9a41e4d0b8..e907efbacb08 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -260,6 +260,8 @@ <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" /> <!-- For handling silent audio recordings --> <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> + <!-- For asking AudioManager audio information --> + <uses-permission android:name="android.permission.QUERY_AUDIO_STATE"/> <!-- to read and change hvac values in a car --> <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" /> diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md index efcb2de0f6c3..4cb765dab741 100644 --- a/packages/SystemUI/docs/qs-tiles.md +++ b/packages/SystemUI/docs/qs-tiles.md @@ -357,10 +357,12 @@ Following are methods that need to be implemented when creating a new SystemUI t Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI. * ```java + @Deprecated public int getMetricsCategory() ``` - Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile. + ~~Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile.~~ + This is now deprecated in favor of `UiEvent` that use the tile spec. * ```java public boolean isAvailable() diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index d5f858c4df94..8ad200951eb4 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -34,7 +34,7 @@ public interface QS extends FragmentBase { String ACTION = "com.android.systemui.action.PLUGIN_QS"; - int VERSION = 12; + int VERSION = 13; String TAG = "QS"; @@ -70,6 +70,11 @@ public interface QS extends FragmentBase { void setContainerController(QSContainerController controller); void setExpandClickListener(OnClickListener onClickListener); + /** + * Returns the height difference between the QSPanel container and the QuickQSPanel container + */ + int getHeightDiff(); + View getHeader(); default void setHasNotifications(boolean hasNotifications) { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 77018d736163..ffac26b2e272 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -40,7 +40,7 @@ import java.util.function.Supplier; @DependsOn(target = Icon.class) @DependsOn(target = State.class) public interface QSTile { - int VERSION = 1; + int VERSION = 2; DetailAdapter getDetailAdapter(); String getTileSpec(); @@ -79,6 +79,12 @@ public interface QSTile { void longClick(@Nullable View view); void userSwitch(int currentUser); + + /** + * @deprecated not needed as {@link com.android.internal.logging.UiEvent} will use + * {@link #getMetricsSpec} + */ + @Deprecated int getMetricsCategory(); void setListening(Object client, boolean listening); @@ -117,7 +123,6 @@ public interface QSTile { void onShowDetail(boolean show); void onToggleStateChanged(boolean state); void onScanStateChanged(boolean state); - void onAnnouncementRequested(CharSequence announcement); } @ProvidesInterface(version = Icon.VERSION) diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml deleted file mode 100644 index 384e02d62265..000000000000 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright (C) 2014 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 - --> - -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@android:color/transparent" - android:clipChildren="false" - android:clipToPadding="false"> - - <include - layout="@layout/keyguard_host_view" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> -</FrameLayout> - diff --git a/packages/SystemUI/res/drawable/ic_qs_color_correction.xml b/packages/SystemUI/res/drawable/ic_qs_color_correction.xml new file mode 100644 index 000000000000..f83cabd7df05 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_color_correction.xml @@ -0,0 +1,24 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#ffffff" + android:pathData="M3,21v-4.75l8.95,-8.95 -1.45,-1.4 1.45,-1.4 1.9,1.9 3.1,-3.1q0.275,-0.275 0.7,-0.275 0.425,0 0.7,0.275l2.35,2.35q0.275,0.275 0.275,0.7 0,0.425 -0.275,0.7l-3.075,3.075 1.9,1.95L18.1,13.5l-1.4,-1.45L7.75,21zM5,19h1.95l8.3,-8.35 -1.9,-1.9L5,17.05z"/> +</vector> diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml index 6b054a91d46b..619591d4f7ec 100644 --- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml +++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml @@ -20,4 +20,6 @@ android:id="@+id/qs_pager" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1"/> + android:layout_weight="1" + android:clipChildren="false" + android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index b28cb2f6f483..60860bad9c64 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -101,7 +101,10 @@ <FrameLayout android:id="@+id/keyguard_bouncer_container" android:layout_height="0dp" android:layout_width="match_parent" - android:layout_weight="1" /> + android:layout_weight="1" + android:background="@android:color/transparent" + android:clipChildren="false" + android:clipToPadding="false" /> </LinearLayout> <com.android.systemui.biometrics.AuthRippleView diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index 2f0957caaaae..2eff692301b1 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -42,6 +42,7 @@ <item>@string/config_systemUIVendorServiceComponent</item> <item>com.android.systemui.SliceBroadcastRelayHandler</item> <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> + <item>com.android.systemui.accessibility.WindowMagnification</item> <item>com.android.systemui.toast.ToastUI</item> <item>com.android.systemui.wmshell.WMShell</item> <item>com.android.systemui.media.systemsounds.HomeSoundEffectController</item> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 9f017b29c665..7b8f349777a6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -82,7 +82,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,fgsmanager + internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,fgsmanager,color_correction </string> <!-- The tiles to display in QuickSettings --> @@ -97,6 +97,7 @@ The syntax is setting-name:spec. If the tile is a TileService, the spec should be specified as custom(package/class). Relative class name is supported. --> <string-array name="config_quickSettingsAutoAdd" translatable="false"> + <item>accessibility_display_daltonizer_enabled:color_correction</item> <item>accessibility_display_inversion_enabled:inversion</item> <item>one_handed_mode_enabled:onehanded</item> </string-array> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b64f81a4174e..2bf121d2e9ab 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -425,60 +425,24 @@ <!-- Content description for the close button in the zen mode panel introduction message. [CHAR LIMIT=NONE] --> <string name="accessibility_desc_close">Close</string> - <!-- Announcement made when the wifi is turned off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_wifi_changed_off">Wifi turned off.</string> - <!-- Announcement made when the wifi is turned on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_wifi_changed_on">Wifi turned on.</string> - <!-- Announcement made when the airplane mode changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_airplane_changed_off">Airplane mode turned off.</string> - <!-- Announcement made when the airplane mode changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_airplane_changed_on">Airplane mode turned on.</string> <!-- Content description of the do not disturb tile in quick settings when on in none (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_dnd_none_on">total silence</string> <!-- Content description of the do not disturb tile in quick settings when on in alarms only (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_dnd_alarms_on">alarms only</string> <!-- Content description of the do not disturb tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_dnd">Do Not Disturb.</string> - <!-- Announcement made when do not disturb changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_dnd_changed_off">Do Not Disturb turned off.</string> - <!-- Announcement made when do not disturb changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_dnd_changed_on">Do Not Disturb turned on.</string> <!-- Content description of the bluetooth tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_bluetooth">Bluetooth.</string> <!-- Content description of the bluetooth tile in quick settings when on (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_bluetooth_on">Bluetooth on.</string> - <!-- Announcement made when the bluetooth is turned off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_bluetooth_changed_off">Bluetooth turned off.</string> - <!-- Announcement made when the bluetooth is turned on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_bluetooth_changed_on">Bluetooth turned on.</string> - <!-- Announcement made when the location tile changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_location_changed_off">Location reporting turned off.</string> - <!-- Announcement made when the location tile changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_location_changed_on">Location reporting turned on.</string> <!-- Content description of the alarm tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_alarm">Alarm set for <xliff:g id="time" example="Wed 3:30 PM">%s</xliff:g>.</string> <!-- Content description of zen mode time condition plus button (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_more_time">More time.</string> <!-- Content description of zen mode time condition minus button (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_less_time">Less time.</string> - <!-- Announcement made when the flashlight state changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_flashlight_changed_off">Flashlight turned off.</string> - <!-- Announcement made when the flashlight state changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_flashlight_changed_on">Flashlight turned on.</string> - <!-- Announcement made when the hotspot state changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_hotspot_changed_off">Mobile hotspot turned off.</string> - <!-- Announcement made when the hotspot state changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_hotspot_changed_on">Mobile hotspot turned on.</string> <!-- Announcement made when the screen stopped casting (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_casting_turned_off">Screen casting stopped.</string> - <!-- Announcement made when the work mode changes to off (not shown on the screen). Paused is used as a verb. [CHAR LIMIT=NONE] --> - <!-- Announcement made when the work mode changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> - <!-- Announcement made when the Data Saver changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_data_saver_changed_off">Data Saver turned off.</string> - <!-- Announcement made when the Data Saver changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> - <string name="accessibility_quick_settings_data_saver_changed_on">Data Saver turned on.</string> - <!-- Announcement made when the Sensor Privacy changes to off (not shown on the screen). [CHAR LIMIT=NONE] --> - <!-- Announcement made when the Sensor Privacy changes to on (not shown on the screen). [CHAR LIMIT=NONE] --> <!-- Content description of the display brightness slider (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_brightness">Display brightness</string> @@ -632,6 +596,7 @@ <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_inversion_label">Color inversion</string> <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] --> + <string name="quick_settings_color_correction_label">Color correction</string> <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] --> <string name="quick_settings_more_settings">More settings</string> <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] --> @@ -1273,6 +1238,10 @@ <!-- [CHAR LIMIT=NONE] Importance Tuner setting title --> <string name="tuner_full_importance_settings">Power notification controls</string> + + <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description --> + <string name="rotation_lock_camera_rotation_on">On - Face-based</string> + <string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n<b>Level 5</b> \n- Show at the top of the notification list diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml index 5fdb4978df73..a610caafa6e1 100644 --- a/packages/SystemUI/res/values/tiles_states_strings.xml +++ b/packages/SystemUI/res/values/tiles_states_strings.xml @@ -142,6 +142,16 @@ <item>On</item> </string-array> + <!-- State names for color correction tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear. [CHAR LIMIT=32] --> + <string-array name="tile_states_color_correction"> + <item>Unavailable</item> + <item>Off</item> + <item>On</item> + </string-array> + <!-- State names for (color) inversion tile: unavailable, off, on. This subtitle is shown when the tile is in that particular state but does not set its own subtitle, so some of these may never appear on screen. They should still be translated as diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index c1d9d0d0e142..4ec65d832851 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -282,7 +282,9 @@ public class RemoteAnimationTargetCompat { * @see SurfaceControl#release() */ public void release() { - leash.mSurfaceControl.release(); + if (leash.mSurfaceControl != null) { + leash.mSurfaceControl.release(); + } if (mStartLeash != null) { mStartLeash.release(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index ac946cacdc5b..24e93efa09cb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -30,6 +30,7 @@ import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider import com.android.systemui.unfold.updates.DeviceFoldStateProvider import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider +import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener import java.lang.IllegalStateException import java.util.concurrent.Executor @@ -46,7 +47,8 @@ fun createUnfoldTransitionProgressProvider( deviceStateManager: DeviceStateManager, sensorManager: SensorManager, mainHandler: Handler, - mainExecutor: Executor + mainExecutor: Executor, + tracingTagPrefix: String ): UnfoldTransitionProgressProvider { if (!config.isEnabled) { @@ -76,9 +78,12 @@ fun createUnfoldTransitionProgressProvider( FixedTimingTransitionProgressProvider(foldStateProvider) } return ScaleAwareTransitionProgressProvider( - unfoldTransitionProgressProvider, - context.contentResolver - ) + unfoldTransitionProgressProvider, + context.contentResolver + ).apply { + // Always present callback that logs animation beginning and end. + addCallback(ATraceLoggerTransitionProgressListener(tracingTagPrefix)) + } } fun createConfig(context: Context): UnfoldTransitionConfig = diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index 51eae573f040..dc64f14b830d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -32,12 +32,7 @@ import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener -/** - * Maps fold updates to unfold transition progress using DynamicAnimation. - * - * TODO(b/193793338) Current limitations: - * - doesn't handle postures - */ +/** Maps fold updates to unfold transition progress using DynamicAnimation. */ internal class PhysicsBasedUnfoldTransitionProgressProvider( private val foldStateProvider: FoldStateProvider ) : diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt new file mode 100644 index 000000000000..f3eeb3210ece --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressProvider.kt @@ -0,0 +1,29 @@ +package com.android.systemui.unfold.util + +import android.os.Trace +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener + +/** + * Listener that logs start and end of the fold-unfold transition. + * + * [tracePrefix] arg helps in differentiating those. Currently, this is expected to be logged twice + * for each fold/unfold: in (1) systemui and (2) launcher process. + */ +class ATraceLoggerTransitionProgressListener(tracePrefix: String) : TransitionProgressListener { + + private val traceName = "$tracePrefix#$UNFOLD_TRANSITION_TRACE_NAME" + + override fun onTransitionStarted() { + Trace.beginAsyncSection(traceName, /* cookie= */ 0) + } + + override fun onTransitionFinished() { + Trace.endAsyncSection(traceName, /* cookie= */ 0) + } + + override fun onTransitionProgress(progress: Float) { + Trace.setCounter(traceName, (progress * 100).toLong()) + } +} + +private const val UNFOLD_TRANSITION_TRACE_NAME = "FoldUnfoldTransitionInProgress" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java deleted file mode 100644 index 4e375c2d1227..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 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 com.android.keyguard; - -import android.view.ViewGroup; - -import com.android.keyguard.dagger.KeyguardBouncerScope; -import com.android.systemui.dagger.qualifiers.RootView; -import com.android.systemui.statusbar.phone.KeyguardBouncer; -import com.android.systemui.util.ViewController; - -import javax.inject.Inject; -/** Controller for a {@link KeyguardBouncer}'s Root view. */ -@KeyguardBouncerScope -public class KeyguardRootViewController extends ViewController<ViewGroup> { - @Inject - public KeyguardRootViewController(@RootView ViewGroup view) { - super(view); - } - - public ViewGroup getView() { - return mView; - } - - @Override - protected void onViewAttached() { - - } - - @Override - protected void onViewDetached() { - - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 2789e27178cc..98721fd78a93 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -94,7 +94,6 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; -import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; @@ -173,7 +172,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_SIM_SUBSCRIPTION_INFO_CHANGED = 328; private static final int MSG_AIRPLANE_MODE_CHANGED = 329; private static final int MSG_SERVICE_STATE_CHANGE = 330; - private static final int MSG_SCREEN_TURNED_ON = 331; private static final int MSG_SCREEN_TURNED_OFF = 332; private static final int MSG_DREAMING_STATE_CHANGED = 333; private static final int MSG_USER_UNLOCKED = 334; @@ -310,7 +308,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mSwitchingUser; private boolean mDeviceInteractive; - private boolean mScreenOn; private SubscriptionManager mSubscriptionManager; private final TelephonyListenerManager mTelephonyListenerManager; private List<SubscriptionInfo> mSubscriptionInfo; @@ -1300,10 +1297,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - public boolean isScreenOn() { - return mScreenOn; - } - private void dispatchErrorMessage(CharSequence message) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1692,29 +1685,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } - private void handleScreenTurnedOn() { - Assert.isMainThread(); - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onScreenTurnedOn(); - } - } - } - private void handleScreenTurnedOff() { - final String tag = "KeyguardUpdateMonitor#handleScreenTurnedOff"; - DejankUtils.startDetectingBlockingIpcs(tag); Assert.isMainThread(); mHardwareFingerprintUnavailableRetryCount = 0; mHardwareFaceUnavailableRetryCount = 0; - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onScreenTurnedOff(); - } - } - DejankUtils.stopDetectingBlockingIpcs(tag); } private void handleDreamingStateChanged(int dreamStart) { @@ -1894,11 +1868,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_SERVICE_STATE_CHANGE: handleServiceStateChange(msg.arg1, (ServiceState) msg.obj); break; - case MSG_SCREEN_TURNED_ON: - handleScreenTurnedOn(); - break; case MSG_SCREEN_TURNED_OFF: - Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_ON"); + Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_OFF"); handleScreenTurnedOff(); Trace.endSection(); break; @@ -3319,17 +3290,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.sendMessage(mHandler.obtainMessage(MSG_FINISHED_GOING_TO_SLEEP, why, 0)); } - public void dispatchScreenTurnedOn() { - synchronized (this) { - mScreenOn = true; - } - mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_ON); - } - public void dispatchScreenTurnedOff() { - synchronized (this) { - mScreenOn = false; - } mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_OFF); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 8170a81a09e6..a74fd15ab11b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -201,22 +201,6 @@ public class KeyguardUpdateMonitorCallback { public void onFinishedGoingToSleep(int why) { } /** - * Called when the screen has been turned on. - * - * @deprecated use {@link com.android.systemui.keyguard.ScreenLifecycle}. - */ - @Deprecated - public void onScreenTurnedOn() { } - - /** - * Called when the screen has been turned off. - * - * @deprecated use {@link com.android.systemui.keyguard.ScreenLifecycle}. - */ - @Deprecated - public void onScreenTurnedOff() { } - - /** * Called when trust changes for a user. */ public void onTrustChanged(int userId) { } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java index fcf1b2c9500a..122f3d7f23f1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java @@ -70,16 +70,6 @@ public interface KeyguardViewController { default void onStartedWakingUp() {}; /** - * Called when the device started turning on. - */ - default void onScreenTurningOn() {}; - - /** - * Called when the device has finished turning on. - */ - default void onScreenTurnedOn() {}; - - /** * Sets whether the Keyguard needs input. * @param needsInput */ diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java index 5160b7e01e89..0cbf8bc07edf 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java @@ -16,10 +16,13 @@ package com.android.keyguard.dagger; +import android.view.ViewGroup; + import com.android.keyguard.KeyguardHostViewController; -import com.android.keyguard.KeyguardRootViewController; +import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.statusbar.phone.KeyguardBouncer; +import dagger.BindsInstance; import dagger.Subcomponent; /** @@ -31,12 +34,9 @@ public interface KeyguardBouncerComponent { /** Simple factory for {@link KeyguardBouncerComponent}. */ @Subcomponent.Factory interface Factory { - KeyguardBouncerComponent create(); + KeyguardBouncerComponent create(@BindsInstance @RootView ViewGroup bouncerContainer); } - /** Returns a {@link KeyguardRootViewController}. */ - KeyguardRootViewController getKeyguardRootViewController(); - /** Returns a {@link KeyguardHostViewController}. */ KeyguardHostViewController getKeyguardHostViewController(); } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java index 4fad9a916d0d..b3c11584bcf8 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java @@ -20,7 +20,6 @@ import android.view.LayoutInflater; import android.view.ViewGroup; import com.android.keyguard.KeyguardHostView; -import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardSecurityContainer; import com.android.keyguard.KeyguardSecurityViewFlipper; import com.android.systemui.R; @@ -35,26 +34,16 @@ import dagger.Provides; */ @Module public interface KeyguardBouncerModule { - /** */ - @Provides - @KeyguardBouncerScope - @RootView - static ViewGroup providesRootView(LayoutInflater layoutInflater) { - return (ViewGroup) layoutInflater.inflate(R.layout.keyguard_bouncer, null); - } - - /** */ - @Provides - @KeyguardBouncerScope - static KeyguardMessageArea providesKeyguardMessageArea(@RootView ViewGroup viewGroup) { - return viewGroup.findViewById(R.id.keyguard_message_area); - } /** */ @Provides @KeyguardBouncerScope - static KeyguardHostView providesKeyguardHostView(@RootView ViewGroup rootView) { - return rootView.findViewById(R.id.keyguard_host_view); + static KeyguardHostView providesKeyguardHostView(@RootView ViewGroup rootView, + LayoutInflater layoutInflater) { + KeyguardHostView hostView = (KeyguardHostView) layoutInflater.inflate( + R.layout.keyguard_host_view, rootView, false); + rootView.addView(hostView); + return hostView; } /** */ diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt new file mode 100644 index 000000000000..705cf6d5df8e --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.mediator + +import android.os.Trace + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ScreenLifecycle +import com.android.systemui.util.concurrency.Execution +import com.android.systemui.util.concurrency.PendingTasksContainer +import com.android.systemui.unfold.SysUIUnfoldComponent +import com.android.systemui.util.kotlin.getOrNull + +import java.util.Optional + +import javax.inject.Inject + +/** + * Coordinates screen on/turning on animations for the KeyguardViewMediator. Specifically for + * screen on events, this will invoke the onDrawn Runnable after all tasks have completed. This + * should route back to the KeyguardService, which informs the system_server that keyguard has + * drawn. + */ +@SysUISingleton +class ScreenOnCoordinator @Inject constructor( + screenLifecycle: ScreenLifecycle, + unfoldComponent: Optional<SysUIUnfoldComponent>, + private val execution: Execution +) : ScreenLifecycle.Observer { + + private val unfoldLightRevealAnimation = unfoldComponent.map( + SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation).getOrNull() + private val foldAodAnimationController = unfoldComponent.map( + SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull() + private val pendingTasks = PendingTasksContainer() + + private var wakeAndUnlockingTask: Runnable? = null + var wakeAndUnlocking = false + set(value) { + if (!value && field) { + // When updating the value back to false, mark the task complete in order to + // callback onDrawn + wakeAndUnlockingTask?.run() + wakeAndUnlockingTask = null + } + field = value + } + + init { + screenLifecycle.addObserver(this) + } + + /** + * When turning on, registers tasks that may need to run before invoking [onDrawn]. + */ + override fun onScreenTurningOn(onDrawn: Runnable) { + execution.assertIsMainThread() + Trace.beginSection("ScreenOnCoordinator#onScreenTurningOn") + + pendingTasks.reset() + + unfoldLightRevealAnimation?.onScreenTurningOn(pendingTasks.registerTask("unfold-reveal")) + foldAodAnimationController?.onScreenTurningOn(pendingTasks.registerTask("fold-to-aod")) + + if (wakeAndUnlocking) { + wakeAndUnlockingTask = pendingTasks.registerTask("wake-and-unlocking") + } + + pendingTasks.onTasksComplete { onDrawn.run() } + Trace.endSection() + } + + override fun onScreenTurnedOn() { + execution.assertIsMainThread() + + foldAodAnimationController?.onScreenTurnedOn() + + pendingTasks.reset() + } + + override fun onScreenTurnedOff() { + wakeAndUnlockingTask = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 4e852a871417..726f8656767c 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -43,15 +43,18 @@ public class Flags { public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING = new BooleanFlag(101, false); - public static final BooleanFlag NOTIFICATION_UPDATES = - new BooleanFlag(102, true); - public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = new BooleanFlag(103, false); public static final ResourceBooleanFlag NOTIFICATION_SHADE_DRAG = new ResourceBooleanFlag(104, R.bool.config_enableNotificationShadeDrag); + public static final BooleanFlag NSSL_DEBUG_LINES = + new BooleanFlag(105, false); + + public static final BooleanFlag NSSL_DEBUG_REMOVE_ANIMATION = + new BooleanFlag(106, false); + /***************************************/ // 200 - keyguard/lockscreen public static final BooleanFlag KEYGUARD_LAYOUT = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java index 5019e65c7182..32b58c2d1bca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java @@ -19,7 +19,10 @@ package com.android.systemui.keyguard; import android.os.Handler; import android.os.Message; import android.os.PowerManager; +import android.os.RemoteException; +import android.util.Log; +import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.systemui.dagger.SysUISingleton; import javax.inject.Inject; @@ -39,6 +42,7 @@ public class KeyguardLifecyclesDispatcher { static final int FINISHED_WAKING_UP = 5; static final int STARTED_GOING_TO_SLEEP = 6; static final int FINISHED_GOING_TO_SLEEP = 7; + private static final String TAG = "KeyguardLifecyclesDispatcher"; private final ScreenLifecycle mScreenLifecycle; private final WakefulnessLifecycle mWakefulnessLifecycle; @@ -65,12 +69,38 @@ public class KeyguardLifecyclesDispatcher { message.sendToTarget(); } + /** + * @param what Message to send. + * @param object Object to send with the message + */ + void dispatch(int what, Object object) { + mHandler.obtainMessage(what, object).sendToTarget(); + } + private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { + final Object obj = msg.obj; switch (msg.what) { case SCREEN_TURNING_ON: - mScreenLifecycle.dispatchScreenTurningOn(); + // Ensure the drawn callback is only ever called once + mScreenLifecycle.dispatchScreenTurningOn(new Runnable() { + boolean mInvoked; + @Override + public void run() { + if (obj == null) return; + if (!mInvoked) { + mInvoked = true; + try { + ((IKeyguardDrawnCallback) obj).onDrawn(); + } catch (RemoteException e) { + Log.w(TAG, "Exception calling onDrawn():", e); + } + } else { + Log.w(TAG, "KeyguardDrawnCallback#onDrawn() invoked > 1 times"); + } + } + }); break; case SCREEN_TURNED_ON: mScreenLifecycle.dispatchScreenTurnedOn(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 22a69d4012fa..e88011e0d3fe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -500,8 +500,8 @@ public class KeyguardService extends Service { public void onScreenTurningOn(IKeyguardDrawnCallback callback) { Trace.beginSection("KeyguardService.mBinder#onScreenTurningOn"); checkPermission(); - mKeyguardViewMediator.onScreenTurningOn(callback); - mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_ON); + mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_ON, + callback); Trace.endSection(); } @@ -509,7 +509,6 @@ public class KeyguardService extends Service { public void onScreenTurnedOn() { Trace.beginSection("KeyguardService.mBinder#onScreenTurnedOn"); checkPermission(); - mKeyguardViewMediator.onScreenTurnedOn(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_ON); Trace.endSection(); } @@ -583,6 +582,18 @@ public class KeyguardService extends Service { checkPermission(); mKeyguardViewMediator.onShortPowerPressedGoHome(); } + + @Override + public void dismissKeyguardToLaunch(Intent intentToLaunch) { + checkPermission(); + mKeyguardViewMediator.dismissKeyguardToLaunch(intentToLaunch); + } + + @Override + public void onSystemKeyPressed(int keycode) { + checkPermission(); + mKeyguardViewMediator.onSystemKeyPressed(keycode); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 0512d488bef9..4658a745b680 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -87,7 +87,6 @@ import androidx.annotation.Nullable; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.internal.policy.IKeyguardDismissCallback; -import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardStateCallback; import com.android.internal.util.LatencyTracker; @@ -99,6 +98,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.ViewMediatorCallback; +import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.animation.Interpolators; @@ -122,15 +122,11 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.unfold.FoldAodAnimationController; -import com.android.systemui.unfold.SysUIUnfoldComponent; -import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.util.DeviceConfigProxy; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Optional; import java.util.concurrent.Executor; import dagger.Lazy; @@ -146,7 +142,7 @@ import dagger.Lazy; * so that once the screen comes on, it will be ready immediately. * * Example queries about the keyguard: - * - is {movement, key} one that should wake the keygaurd? + * - is {movement, key} one that should wake the keyguard? * - is the keyguard showing? * - are input events restricted due to the state of the keyguard? * @@ -199,7 +195,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private static final int RESET = 3; private static final int VERIFY_UNLOCK = 4; private static final int NOTIFY_FINISHED_GOING_TO_SLEEP = 5; - private static final int NOTIFY_SCREEN_TURNING_ON = 6; private static final int KEYGUARD_DONE = 7; private static final int KEYGUARD_DONE_DRAWING = 8; private static final int SET_OCCLUDED = 9; @@ -208,8 +203,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private static final int START_KEYGUARD_EXIT_ANIM = 12; private static final int KEYGUARD_DONE_PENDING_TIMEOUT = 13; private static final int NOTIFY_STARTED_WAKING_UP = 14; - private static final int NOTIFY_SCREEN_TURNED_ON = 15; - private static final int NOTIFY_SCREEN_TURNED_OFF = 16; private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 17; private static final int SYSTEM_READY = 18; private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19; @@ -429,17 +422,10 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, */ private WorkLockActivityController mWorkLockController; - /** - * @see #setPulsing(boolean) - */ - private boolean mPulsing; - private boolean mLockLater; private boolean mShowHomeOverLockscreen; private boolean mInGestureNavigationMode; - private boolean mWakeAndUnlocking; - private Runnable mWakeAndUnlockingDrawnCallback; private CharSequence mCustomMessage; /** @@ -816,14 +802,11 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; - private final Optional<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation; - private final Optional<FoldAodAnimationController> mFoldAodAnimationController; - private final PendingDrawnTasksContainer mPendingDrawnTasks = new PendingDrawnTasksContainer(); - private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; private final InteractionJankMonitor mInteractionJankMonitor; private boolean mWallpaperSupportsAmbientMode; + private ScreenOnCoordinator mScreenOnCoordinator; /** * Injected constructor. See {@link KeyguardModule}. @@ -843,12 +826,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, - Optional<SysUIUnfoldComponent> unfoldComponent, SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy, ScreenOffAnimationController screenOffAnimationController, Lazy<NotificationShadeDepthController> notificationShadeDepthController, + ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor) { super(context); mFalsingCollector = falsingCollector; @@ -865,6 +848,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mKeyguardDisplayManager = keyguardDisplayManager; dumpManager.registerDumpable(getClass().getName(), this); mDeviceConfig = deviceConfig; + mScreenOnCoordinator = screenOnCoordinator; mShowHomeOverLockscreen = mDeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN, @@ -879,11 +863,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, })); mDozeParameters = dozeParameters; - mUnfoldLightRevealAnimation = unfoldComponent - .map(SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation); - mFoldAodAnimationController = unfoldComponent - .map(SysUIUnfoldComponent::getFoldAodAnimationController); - mStatusBarStateController = statusBarStateController; statusBarStateController.addCallback(this); @@ -1076,7 +1055,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, synchronized (this) { mDeviceInteractive = false; mGoingToSleep = false; - mWakeAndUnlocking = false; + mScreenOnCoordinator.setWakeAndUnlocking(false); mAnimatingScreenOff = mDozeParameters.shouldAnimateDozingChange(); resetKeyguardDonePendingLocked(); @@ -1254,21 +1233,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, Trace.endSection(); } - public void onScreenTurningOn(IKeyguardDrawnCallback callback) { - Trace.beginSection("KeyguardViewMediator#onScreenTurningOn"); - notifyScreenOn(callback); - Trace.endSection(); - } - - public void onScreenTurnedOn() { - Trace.beginSection("KeyguardViewMediator#onScreenTurnedOn"); - notifyScreenTurnedOn(); - mUpdateMonitor.dispatchScreenTurnedOn(); - Trace.endSection(); - } - public void onScreenTurnedOff() { - notifyScreenTurnedOff(); mUpdateMonitor.dispatchScreenTurnedOff(); } @@ -1283,7 +1248,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // Skipping the lockscreen because we're not yet provisioned, but we still need to // notify the StrongAuthTracker that it's now safe to run trust agents, in case the // user sets a credential later. - getLockPatternUtils().userPresent(KeyguardUpdateMonitor.getCurrentUser()); + mLockPatternUtils.userPresent(KeyguardUpdateMonitor.getCurrentUser()); } } @@ -1660,24 +1625,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mHandler.sendEmptyMessage(NOTIFY_STARTED_WAKING_UP); } - private void notifyScreenOn(IKeyguardDrawnCallback callback) { - if (DEBUG) Log.d(TAG, "notifyScreenOn"); - Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_TURNING_ON, callback); - mHandler.sendMessage(msg); - } - - private void notifyScreenTurnedOn() { - if (DEBUG) Log.d(TAG, "notifyScreenTurnedOn"); - Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_TURNED_ON); - mHandler.sendMessage(msg); - } - - private void notifyScreenTurnedOff() { - if (DEBUG) Log.d(TAG, "notifyScreenTurnedOff"); - Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_TURNED_OFF); - mHandler.sendMessage(msg); - } - /** * Send message to keyguard telling it to show itself * @see #handleShow @@ -1838,21 +1785,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, case NOTIFY_FINISHED_GOING_TO_SLEEP: handleNotifyFinishedGoingToSleep(); break; - case NOTIFY_SCREEN_TURNING_ON: - Trace.beginSection( - "KeyguardViewMediator#handleMessage NOTIFY_SCREEN_TURNING_ON"); - handleNotifyScreenTurningOn((IKeyguardDrawnCallback) msg.obj); - Trace.endSection(); - break; - case NOTIFY_SCREEN_TURNED_ON: - Trace.beginSection( - "KeyguardViewMediator#handleMessage NOTIFY_SCREEN_TURNED_ON"); - handleNotifyScreenTurnedOn(); - Trace.endSection(); - break; - case NOTIFY_SCREEN_TURNED_OFF: - handleNotifyScreenTurnedOff(); - break; case NOTIFY_STARTED_WAKING_UP: Trace.beginSection( "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP"); @@ -1984,7 +1916,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) { mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId)); } - getLockPatternUtils().userPresent(currentUserId); + mLockPatternUtils.userPresent(currentUserId); }); } else { mBootSendUserPresent = true; @@ -2081,7 +2013,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mHiding = false; mKeyguardExitAnimationRunner = null; - mWakeAndUnlocking = false; + mScreenOnCoordinator.setWakeAndUnlocking(false); mPendingLock = false; setShowingLocked(true); mKeyguardViewControllerLazy.get().show(options); @@ -2113,12 +2045,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, int flags = 0; if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() - || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) { + || mScreenOnCoordinator.getWakeAndUnlocking() + && !mWallpaperSupportsAmbientMode) { flags |= WindowManagerPolicyConstants .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade() - || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) { + || mScreenOnCoordinator.getWakeAndUnlocking() + && mWallpaperSupportsAmbientMode) { // When the wallpaper supports ambient mode, the scrim isn't fully opaque during // wake and unlock and we should fade in the app on top of the wallpaper flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; @@ -2229,14 +2163,13 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner; mKeyguardExitAnimationRunner = null; - if (mWakeAndUnlocking && mWakeAndUnlockingDrawnCallback != null) { + if (mScreenOnCoordinator.getWakeAndUnlocking()) { // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report // the next draw from here so we don't have to wait for window manager to signal // this to our ViewRootImpl. mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw(); - mWakeAndUnlockingDrawnCallback.run(); - mWakeAndUnlockingDrawnCallback = null; + mScreenOnCoordinator.setWakeAndUnlocking(false); } LatencyTracker.getInstance(mContext) @@ -2360,7 +2293,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } setShowingLocked(false); - mWakeAndUnlocking = false; + mScreenOnCoordinator.setWakeAndUnlocking(false); mDismissCallbackRegistry.notifyDismissSucceeded(); resetKeyguardDonePendingLocked(); mHideAnimationRun = false; @@ -2567,66 +2500,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, Trace.endSection(); } - private void handleNotifyScreenTurningOn(IKeyguardDrawnCallback callback) { - Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurningOn"); - synchronized (KeyguardViewMediator.this) { - if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn"); - - mPendingDrawnTasks.reset(); - - if (mUnfoldLightRevealAnimation.isPresent()) { - mUnfoldLightRevealAnimation.get() - .onScreenTurningOn(mPendingDrawnTasks.registerTask("unfold-reveal")); - } - - if (mFoldAodAnimationController.isPresent()) { - mFoldAodAnimationController.get() - .onScreenTurningOn(mPendingDrawnTasks.registerTask("fold-to-aod")); - } - - mKeyguardViewControllerLazy.get().onScreenTurningOn(); - if (callback != null) { - if (mWakeAndUnlocking) { - mWakeAndUnlockingDrawnCallback = - mPendingDrawnTasks.registerTask("wake-and-unlocking"); - } - } - - mPendingDrawnTasks.onTasksComplete(() -> notifyDrawn(callback)); - } - Trace.endSection(); - } - - private void handleNotifyScreenTurnedOn() { - Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn"); - synchronized (this) { - if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn"); - - mPendingDrawnTasks.reset(); - mKeyguardViewControllerLazy.get().onScreenTurnedOn(); - } - Trace.endSection(); - } - - private void handleNotifyScreenTurnedOff() { - synchronized (this) { - if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOff"); - mWakeAndUnlockingDrawnCallback = null; - } - } - - private void notifyDrawn(final IKeyguardDrawnCallback callback) { - Trace.beginSection("KeyguardViewMediator#notifyDrawn"); - try { - if (callback != null) { - callback.onDrawn(); - } - } catch (RemoteException e) { - Slog.w(TAG, "Exception calling onDrawn():", e); - } - Trace.endSection(); - } - private void resetKeyguardDonePendingLocked() { mKeyguardDonePending = false; mHandler.removeMessages(KEYGUARD_DONE_PENDING_TIMEOUT); @@ -2650,7 +2523,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, public void onWakeAndUnlocking() { Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking"); - mWakeAndUnlocking = true; + mScreenOnCoordinator.setWakeAndUnlocking(true); keyguardDone(); Trace.endSection(); } @@ -2750,12 +2623,16 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // do nothing } - public ViewMediatorCallback getViewMediatorCallback() { - return mViewMediatorCallback; + public void dismissKeyguardToLaunch(Intent intentToLaunch) { + // do nothing } - public LockPatternUtils getLockPatternUtils() { - return mLockPatternUtils; + public void onSystemKeyPressed(int keycode) { + // do nothing + } + + public ViewMediatorCallback getViewMediatorCallback() { + return mViewMediatorCallback; } @Override @@ -2781,9 +2658,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun); pw.print(" mPendingReset: "); pw.println(mPendingReset); pw.print(" mPendingLock: "); pw.println(mPendingLock); - pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.getPendingCount()); - pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking); - pw.print(" mDrawnCallback: "); pw.println(mWakeAndUnlockingDrawnCallback); + pw.print(" wakeAndUnlocking: "); pw.println(mScreenOnCoordinator.getWakeAndUnlocking()); } /** @@ -2819,13 +2694,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** - * @param pulsing true when device temporarily wakes up to display an incoming notification. - */ - public void setPulsing(boolean pulsing) { - mPulsing = pulsing; - } - - /** * Set if the wallpaper supports ambient mode. This is used to trigger the right animation. * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it * with the light reveal scrim. @@ -2864,7 +2732,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } private void setShowingLocked(boolean showing, boolean forceCallbacks) { - final boolean aodShowing = mDozing && !mWakeAndUnlocking; + final boolean aodShowing = mDozing && !mScreenOnCoordinator.getWakeAndUnlocking(); final boolean notifyDefaultDisplayCallbacks = showing != mShowing || aodShowing != mAodShowing || forceCallbacks; mShowing = showing; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java index 3da6caf31968..b870f589b746 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java @@ -20,6 +20,7 @@ import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -42,4 +43,13 @@ public class Lifecycle<T> { consumer.accept(mObservers.get(i)); } } + + /** + * Will dispatch the consumer to the observer, along with a single argument of type<U>. + */ + public <U> void dispatch(BiConsumer<T, U> biConsumer, U arg) { + for (int i = 0; i < mObservers.size(); i++) { + biConsumer.accept(mObservers.get(i), arg); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java index d17c39a81dae..e71aa854e94a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java @@ -18,6 +18,8 @@ package com.android.systemui.keyguard; import android.os.Trace; +import androidx.annotation.NonNull; + import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; @@ -49,9 +51,14 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme return mScreenState; } - public void dispatchScreenTurningOn() { + /** + * Dispatch screen turning on events to the registered observers + * + * @param onDrawn Invoke to notify the caller that the event has been processed + */ + public void dispatchScreenTurningOn(@NonNull Runnable onDrawn) { setScreenState(SCREEN_TURNING_ON); - dispatch(Observer::onScreenTurningOn); + dispatch(Observer::onScreenTurningOn, onDrawn); } public void dispatchScreenTurnedOn() { @@ -81,7 +88,12 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme } public interface Observer { - default void onScreenTurningOn() {} + /** + * Receive the screen turning on event + * + * @param onDrawn Invoke to notify the caller that the event has been processed + */ + default void onScreenTurningOn(@NonNull Runnable onDrawn) {} default void onScreenTurnedOn() {} default void onScreenTurningOff() {} default void onScreenTurnedOff() {} 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 eecb55b3530a..dd844e8d49ab 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -31,6 +31,7 @@ import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; +import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingModule; @@ -50,11 +51,9 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.sensors.AsyncSensorManager; -import java.util.Optional; import java.util.concurrent.Executor; import dagger.Lazy; @@ -93,12 +92,12 @@ public class KeyguardModule { NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, - Optional<SysUIUnfoldComponent> unfoldComponent, SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, ScreenOffAnimationController screenOffAnimationController, Lazy<NotificationShadeDepthController> notificationShadeDepthController, + ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor) { return new KeyguardViewMediator( context, @@ -117,12 +116,12 @@ public class KeyguardModule { navigationModeController, keyguardDisplayManager, dozeParameters, - unfoldComponent, statusBarStateController, keyguardStateController, keyguardUnlockAnimationController, screenOffAnimationController, notificationShadeDepthController, + screenOnCoordinator, interactionJankMonitor ); } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt index 492fdc699935..b15807c0475c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -66,11 +66,12 @@ import java.util.Locale * @param poolSize The maximum amount that the size of the buffer is allowed to flex in response to * sequential calls to [document] that aren't immediately followed by a matching call to [push]. */ -class LogBuffer( +class LogBuffer @JvmOverloads constructor( private val name: String, private val maxLogs: Int, private val poolSize: Int, - private val logcatEchoTracker: LogcatEchoTracker + private val logcatEchoTracker: LogcatEchoTracker, + private val systrace: Boolean = true ) { init { if (maxLogs < poolSize) { @@ -175,6 +176,10 @@ class LogBuffer( buffer.removeFirst() } buffer.add(message as LogMessageImpl) + if (systrace) { + val messageStr = message.printer(message) + Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "$name - $messageStr") + } if (logcatEchoTracker.isBufferLoggable(name, message.level) || logcatEchoTracker.isTagLoggable(message.tag, message.level)) { echo(message) @@ -237,7 +242,6 @@ class LogBuffer( LogLevel.ERROR -> Log.e(message.tag, strMessage) LogLevel.WTF -> Log.wtf(message.tag, strMessage) } - Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", strMessage) } } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt index 5296ee603f40..cbfca25e0c60 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -36,8 +36,13 @@ class LogBufferFactory @Inject constructor( } @JvmOverloads - fun create(name: String, maxPoolSize: Int, flexSize: Int = 10): LogBuffer { - val buffer = LogBuffer(name, poolLimit(maxPoolSize), flexSize, logcatEchoTracker) + fun create( + name: String, + maxPoolSize: Int, + flexSize: Int = 10, + systrace: Boolean = true + ): LogBuffer { + val buffer = LogBuffer(name, poolLimit(maxPoolSize), flexSize, logcatEchoTracker, systrace) dumpManager.registerBuffer(name, buffer) return buffer } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index d3bd0a5e31a0..1f953d7ee6c9 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -49,7 +49,8 @@ public class LogModule { @SysUISingleton @NotificationLog public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) { - return factory.create("NotifLog", 1000); + return factory.create("NotifLog", 1000 /* maxPoolSize */, + 10 /* flexSize */, false /* systrace */); } /** Provides a logging buffer for all logs related to the data layer of notifications. */ @@ -65,7 +66,8 @@ public class LogModule { @SysUISingleton @NotificationSectionLog public static LogBuffer provideNotificationSectionLogBuffer(LogBufferFactory factory) { - return factory.create("NotifSectionLog", 1000); + return factory.create("NotifSectionLog", 1000 /* maxPoolSize */, + 10 /* flexSize */, false /* systrace */); } /** Provides a logging buffer for all logs related to the data layer of notifications. */ @@ -81,7 +83,8 @@ public class LogModule { @SysUISingleton @QSLog public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) { - return factory.create("QSLog", 500); + return factory.create("QSLog", 500 /* maxPoolSize */, + 10 /* flexSize */, false /* systrace */); } /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */ @@ -89,7 +92,8 @@ public class LogModule { @SysUISingleton @BroadcastDispatcherLog public static LogBuffer provideBroadcastDispatcherLogBuffer(LogBufferFactory factory) { - return factory.create("BroadcastDispatcherLog", 500); + return factory.create("BroadcastDispatcherLog", 500 /* maxPoolSize */, + 10 /* flexSize */, false /* systrace */); } /** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */ @@ -127,7 +131,8 @@ public class LogModule { @SysUISingleton @QSFragmentDisableLog public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) { - return factory.create("QSFragmentDisableFlagsLog", 10); + return factory.create("QSFragmentDisableFlagsLog", 10 /* maxPoolSize */, + 10 /* flexSize */, false /* systrace */); } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 99b283e8ec02..5a86723677b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -71,20 +71,32 @@ class MediaTttCommandLineHelper @Inject constructor( when (args[1]) { MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> { mediaTttChipControllerSender.displayChip( - MoveCloserToTransfer(appIconDrawable, otherDeviceName) + MoveCloserToTransfer( + appIconDrawable, APP_ICON_CONTENT_DESCRIPTION, otherDeviceName + ) ) } TRANSFER_INITIATED_COMMAND_NAME -> { val futureTask = FutureTask { fakeUndoRunnable } mediaTttChipControllerSender.displayChip( - TransferInitiated(appIconDrawable, otherDeviceName, futureTask) + TransferInitiated( + appIconDrawable, + APP_ICON_CONTENT_DESCRIPTION, + otherDeviceName, + futureTask + ) ) mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME) } TRANSFER_SUCCEEDED_COMMAND_NAME -> { mediaTttChipControllerSender.displayChip( - TransferSucceeded(appIconDrawable, otherDeviceName, fakeUndoRunnable) + TransferSucceeded( + appIconDrawable, + APP_ICON_CONTENT_DESCRIPTION, + otherDeviceName, + fakeUndoRunnable + ) ) } else -> { @@ -118,7 +130,9 @@ class MediaTttCommandLineHelper @Inject constructor( /** A command to DISPLAY the media ttt chip on the RECEIVER device. */ inner class AddChipCommandReceiver : Command { override fun execute(pw: PrintWriter, args: List<String>) { - mediaTttChipControllerReceiver.displayChip(ChipStateReceiver(appIconDrawable)) + mediaTttChipControllerReceiver.displayChip( + ChipStateReceiver(appIconDrawable, APP_ICON_CONTENT_DESCRIPTION) + ) } override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_RECEIVER_TAG") @@ -156,4 +170,5 @@ val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!! val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!! private const val FUTURE_WAIT_TIME = 2000L +private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon" private const val TAG = "MediaTapToTransferCli" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 3b429c887796..67721a543427 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -99,6 +99,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( internal fun setIcon(chipState: T, currentChipView: ViewGroup) { currentChipView.findViewById<CachingIconView>(R.id.app_icon).apply { this.setImageDrawable(chipState.appIconDrawable) + this.contentDescription = chipState.appIconContentDescription } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt index 1e475a5e7c82..c510cbba9c35 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt @@ -22,7 +22,9 @@ import android.graphics.drawable.Drawable * A superclass chip state that will be subclassed by the sender chip and receiver chip. * * @property appIconDrawable a drawable representing the icon of the app playing the media. + * @property appIconContentDescription a string to use as the content description for the icon. */ open class MediaTttChipState( - internal val appIconDrawable: Drawable + internal val appIconDrawable: Drawable, + internal val appIconContentDescription: String ) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt index 5397235e0137..df6b93431c93 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt @@ -24,5 +24,6 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipState * the receiver device. */ class ChipStateReceiver( - appIconDrawable: Drawable -) : MediaTttChipState(appIconDrawable) + appIconDrawable: Drawable, + appIconContentDescription: String +) : MediaTttChipState(appIconDrawable, appIconContentDescription) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 24943b9e2a1e..b1f6faaba924 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -34,9 +34,10 @@ import java.util.concurrent.Future */ sealed class ChipStateSender( appIconDrawable: Drawable, + appIconContentDescription: String, @StringRes internal val chipText: Int, internal val otherDeviceName: String, -) : MediaTttChipState(appIconDrawable) +) : MediaTttChipState(appIconDrawable, appIconContentDescription) /** * A state representing that the two devices are close but not close enough to initiate a transfer. @@ -44,8 +45,14 @@ sealed class ChipStateSender( */ class MoveCloserToTransfer( appIconDrawable: Drawable, + appIconContentDescription: String, otherDeviceName: String, -) : ChipStateSender(appIconDrawable, R.string.media_move_closer_to_transfer, otherDeviceName) +) : ChipStateSender( + appIconDrawable, + appIconContentDescription, + R.string.media_move_closer_to_transfer, + otherDeviceName +) /** * A state representing that a transfer has been initiated (but not completed). @@ -57,9 +64,15 @@ class MoveCloserToTransfer( */ class TransferInitiated( appIconDrawable: Drawable, + appIconContentDescription: String, otherDeviceName: String, val future: Future<Runnable?> -) : ChipStateSender(appIconDrawable, R.string.media_transfer_playing, otherDeviceName) +) : ChipStateSender( + appIconDrawable, + appIconContentDescription, + R.string.media_transfer_playing, + otherDeviceName +) /** * A state representing that a transfer has been successfully completed. @@ -69,6 +82,11 @@ class TransferInitiated( */ class TransferSucceeded( appIconDrawable: Drawable, + appIconContentDescription: String, otherDeviceName: String, val undoRunnable: Runnable? = null -) : ChipStateSender(appIconDrawable, R.string.media_transfer_playing, otherDeviceName) +) : ChipStateSender(appIconDrawable, + appIconContentDescription, + R.string.media_transfer_playing, + otherDeviceName +) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index fce4b98f9abe..77d3d70fc98c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -93,7 +93,10 @@ class MediaTttChipControllerSender @Inject constructor( mainExecutor.execute { displayChip( TransferSucceeded( - chipState.appIconDrawable, chipState.otherDeviceName, undoRunnable + chipState.appIconDrawable, + chipState.appIconContentDescription, + chipState.otherDeviceName, + undoRunnable ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 3e2da7204c05..6d1f8f7d01b1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -30,6 +30,7 @@ import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import java.util.ArrayList; +import java.util.List; import java.util.Set; public class PagedTileLayout extends ViewPager implements QSTileLayout { @@ -332,6 +333,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageListener = listener; } + public List<String> getSpecsForPage(int page) { + ArrayList<String> out = new ArrayList<>(); + if (page < 0) return out; + int perPage = mPages.get(0).maxTiles(); + int startOfPage = page * perPage; + int endOfPage = (page + 1) * perPage; + for (int i = startOfPage; i < endOfPage && i < mTiles.size(); i++) { + out.add(mTiles.get(i).tile.getTileSpec()); + } + return out; + } + private void distributeTiles() { emptyAndInflateOrRemovePages(); @@ -491,6 +504,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { return currentPage.mRecords.size(); } + public int getNumTilesFirstPage() { + if (mPages.size() == 0) return 0; + return mPages.get(0).mRecords.size(); + } + public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) { if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) { // Do not start the reveal animation unless there are tiles to animate, multiple @@ -556,8 +574,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { updateSelected(); if (mPageIndicator == null) return; if (mPageListener != null) { + int pageNumber = isLayoutRtl() ? mPages.size() - 1 - position : position; mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1 - : position == 0); + : position == 0, pageNumber); } } @@ -575,8 +594,12 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicatorPosition = position + positionOffset; mPageIndicator.setLocation(mPageIndicatorPosition); if (mPageListener != null) { + int pageNumber = isLayoutRtl() ? mPages.size() - 1 - position : position; mPageListener.onPageChanged(positionOffsetPixels == 0 && - (isLayoutRtl() ? position == mPages.size() - 1 : position == 0)); + (isLayoutRtl() ? position == mPages.size() - 1 : position == 0), + // Send only valid page number on integer pages + positionOffsetPixels == 0 ? pageNumber : PageListener.INVALID_PAGE + ); } } @@ -626,6 +649,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { }; public interface PageListener { - void onPageChanged(boolean isFirst); + int INVALID_PAGE = -1; + void onPageChanged(boolean isFirst, int pageNumber); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 44d5e21e6354..6c7d6e01e9ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -20,6 +20,8 @@ import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FOOTER; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; @@ -57,11 +59,14 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim"; private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows"; - public static final float EXPANDED_TILE_DELAY = .86f; + private static final float EXPANDED_TILE_DELAY = .86f; + //Non first page delays + private static final float QS_TILE_LABEL_FADE_OUT_START = 0.15f; + private static final float QS_TILE_LABEL_FADE_OUT_END = 0.7f; + private static final float QQS_FADE_IN_INTERVAL = 0.1f; + public static final float SHORT_PARALLAX_AMOUNT = 0.1f; - private static final long QQS_FADE_IN_DURATION = 200L; - // Fade out faster than fade in to finish before QQS hides. - private static final long QQS_FADE_OUT_DURATION = 50L; + /** * List of all views that will be reset when clearing animation state @@ -86,33 +91,54 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private PagedTileLayout mPagedLayout; private boolean mOnFirstPage = true; - private QSExpansionPathInterpolator mQSExpansionPathInterpolator; + private int mCurrentPage = 0; + private final QSExpansionPathInterpolator mQSExpansionPathInterpolator; + // Animator for elements in the first page, including secondary labels and qqs brightness + // slider, as well as animating the alpha of the QS tile layout (as we are tracking QQS tiles) private TouchAnimator mFirstPageAnimator; - private TouchAnimator mFirstPageDelayedAnimator; + // TranslationX animator for QQS/QS tiles private TouchAnimator mTranslationXAnimator; + // TranslationY animator for QS tiles (and their components) in the first page private TouchAnimator mTranslationYAnimator; - private TouchAnimator mNonfirstPageAnimator; - private TouchAnimator mNonfirstPageDelayedAnimator; + // TranslationY animator for QQS tiles (and their components) + private TouchAnimator mQQSTranslationYAnimator; + // Animates alpha of permanent views (QS tile layout, QQS tiles) when not in first page + private TouchAnimator mNonfirstPageAlphaAnimator; + // TranslatesY the QS Tile layout using QS.getHeightDiff() + private TouchAnimator mQSTileLayoutTranslatorAnimator; // This animates fading of SecurityFooter and media divider private TouchAnimator mAllPagesDelayedAnimator; + // Animator for brightness slider(s) private TouchAnimator mBrightnessAnimator; + // Animator for Footer actions in QQS private TouchAnimator mQQSFooterActionsAnimator; + // Height animator for QQS tiles (height changing from QQS size to QS size) private HeightExpansionAnimator mQQSTileHeightAnimator; - private HeightExpansionAnimator mOtherTilesExpandAnimator; + // Height animator for QS tile in first page but not in QQS, to present the illusion that they + // are expanding alongside the QQS tiles + private HeightExpansionAnimator mOtherFirstPageTilesHeightAnimator; + // Pair of animators for each non first page. The creation is delayed until the user first + // scrolls to that page, in order to get the proper measures and layout. + private final SparseArray<Pair<HeightExpansionAnimator, TouchAnimator>> + mNonFirstPageQSAnimators = new SparseArray<>(); private boolean mNeedsAnimatorUpdate = false; - private boolean mToShowing; private boolean mOnKeyguard; private boolean mAllowFancy; private boolean mFullRows; private int mNumQuickTiles; + private int mLastQQSTileHeight; private float mLastPosition; private final QSTileHost mHost; private final Executor mExecutor; private final TunerService mTunerService; private boolean mShowCollapsedOnKeyguard; private boolean mTranslateWhileExpanding; + private int mQQSTop; + + private int[] mTmpLoc1 = new int[2]; + private int[] mTmpLoc2 = new int[2]; @Inject public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, @@ -213,8 +239,19 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateAnimators(); } + private void addNonFirstPageAnimators(int page) { + Pair<HeightExpansionAnimator, TouchAnimator> pair = createSecondaryPageAnimators(page); + mNonFirstPageQSAnimators.put(page, pair); + } + @Override - public void onPageChanged(boolean isFirst) { + public void onPageChanged(boolean isFirst, int currentPage) { + if (currentPage != INVALID_PAGE && mCurrentPage != currentPage) { + mCurrentPage = currentPage; + if (!isFirst && !mNonFirstPageQSAnimators.contains(currentPage)) { + addNonFirstPageAnimators(currentPage); + } + } if (mOnFirstPage == isFirst) return; if (!isFirst) { clearAnimationState(); @@ -230,7 +267,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha int yOffset, int[] temp, TouchAnimator.Builder animatorBuilderX, - TouchAnimator.Builder animatorBuilderY + TouchAnimator.Builder animatorBuilderY, + TouchAnimator.Builder qqsAnimatorBuilderY ) { getRelativePosition(temp, qqsView, commonParent); int qqsPosX = temp[0]; @@ -243,7 +281,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha animatorBuilderX.addFloat(qqsView, "translationX", 0, xDiff); animatorBuilderX.addFloat(qsView, "translationX", -xDiff, 0); int yDiff = qsPosY - qqsPosY - yOffset; - animatorBuilderY.addFloat(qqsView, "translationY", 0, yDiff); + qqsAnimatorBuilderY.addFloat(qqsView, "translationY", 0, yDiff); animatorBuilderY.addFloat(qsView, "translationY", -yDiff, 0); mAllViews.add(qqsView); mAllViews.add(qsView); @@ -253,40 +291,47 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mNeedsAnimatorUpdate = false; TouchAnimator.Builder firstPageBuilder = new Builder(); TouchAnimator.Builder translationYBuilder = new Builder(); + TouchAnimator.Builder qqsTranslationYBuilder = new Builder(); TouchAnimator.Builder translationXBuilder = new Builder(); + TouchAnimator.Builder nonFirstPageAlphaBuilder = new Builder(); Collection<QSTile> tiles = mHost.getTiles(); int count = 0; - int[] loc1 = new int[2]; - int[] loc2 = new int[2]; clearAnimationState(); + mNonFirstPageQSAnimators.clear(); mAllViews.clear(); mAnimatedQsViews.clear(); mQQSTileHeightAnimator = null; - mOtherTilesExpandAnimator = null; + mOtherFirstPageTilesHeightAnimator = null; mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(); QSTileLayout tileLayout = mQsPanelController.getTileLayout(); mAllViews.add((View) tileLayout); - int height = mQs.getView() != null ? mQs.getView().getMeasuredHeight() : 0; - int heightDiff = height - mQs.getHeader().getBottom() - + mQs.getHeader().getPaddingBottom(); + int heightDiff = mQs.getHeightDiff(); if (!mTranslateWhileExpanding) { heightDiff *= SHORT_PARALLAX_AMOUNT; } - firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0); + mQSTileLayoutTranslatorAnimator = new Builder() + .addFloat(tileLayout, "translationY", heightDiff, 0) + .build(); - int qqsTileHeight = 0; + mLastQQSTileHeight = 0; if (mQsPanelController.areThereTiles()) { for (QSTile tile : tiles) { QSTileView tileView = mQsPanelController.getTileView(tile); + if (tileView == null) { Log.e(TAG, "tileView is null " + tile.getTileSpec()); continue; } + // Only animate tiles in the first page + if (mPagedLayout != null && count >= mPagedLayout.getNumTilesFirstPage()) { + break; + } + final View tileIcon = tileView.getIcon().getIconView(); View view = mQs.getView(); @@ -297,16 +342,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha QSTileView quickTileView = mQuickQSPanelController.getTileView(tile); if (quickTileView == null) continue; - getRelativePosition(loc1, quickTileView, view); - getRelativePosition(loc2, tileView, view); - int yOffset = loc2[1] - loc1[1]; - int xOffset = loc2[0] - loc1[0]; + getRelativePosition(mTmpLoc1, quickTileView, view); + getRelativePosition(mTmpLoc2, tileView, view); + int yOffset = mTmpLoc2[1] - mTmpLoc1[1]; + int xOffset = mTmpLoc2[0] - mTmpLoc1[0]; // Offset the translation animation on the views // (that goes from 0 to getOffsetTranslation) int offsetWithQSBHTranslation = yOffset - mQuickStatusBarHeader.getOffsetTranslation(); - translationYBuilder.addFloat(quickTileView, "translationY", 0, + qqsTranslationYBuilder.addFloat(quickTileView, "translationY", 0, offsetWithQSBHTranslation); translationYBuilder.addFloat(tileView, "translationY", -offsetWithQSBHTranslation, 0); @@ -317,7 +362,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if (mQQSTileHeightAnimator == null) { mQQSTileHeightAnimator = new HeightExpansionAnimator(this, quickTileView.getMeasuredHeight(), tileView.getMeasuredHeight()); - qqsTileHeight = quickTileView.getMeasuredHeight(); + mLastQQSTileHeight = quickTileView.getMeasuredHeight(); } mQQSTileHeightAnimator.addView(quickTileView); @@ -329,9 +374,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha view, xOffset, yOffset, - loc1, + mTmpLoc1, translationXBuilder, - translationYBuilder + translationYBuilder, + qqsTranslationYBuilder ); // Label containers @@ -341,9 +387,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha view, xOffset, yOffset, - loc1, + mTmpLoc1, translationXBuilder, - translationYBuilder + translationYBuilder, + qqsTranslationYBuilder ); // Secondary icon @@ -353,12 +400,15 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha view, xOffset, yOffset, - loc1, + mTmpLoc1, translationXBuilder, - translationYBuilder + translationYBuilder, + qqsTranslationYBuilder ); firstPageBuilder.addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1); + nonFirstPageAlphaBuilder + .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 0); mAnimatedQsViews.add(tileView); mAllViews.add(quickTileView); @@ -373,16 +423,17 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // expanding from. SideLabelTileLayout qqsLayout = (SideLabelTileLayout) mQuickQsPanel.getTileLayout(); - getRelativePosition(loc1, qqsLayout, view); - getRelativePosition(loc2, tileView, view); - int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count)); + getRelativePosition(mTmpLoc1, qqsLayout, view); + mQQSTop = mTmpLoc1[1]; + getRelativePosition(mTmpLoc2, tileView, view); + int diff = mTmpLoc2[1] - (mTmpLoc1[1] + qqsLayout.getPhantomTopPosition(count)); translationYBuilder.addFloat(tileView, "translationY", -diff, 0); - if (mOtherTilesExpandAnimator == null) { - mOtherTilesExpandAnimator = + if (mOtherFirstPageTilesHeightAnimator == null) { + mOtherFirstPageTilesHeightAnimator = new HeightExpansionAnimator( - this, qqsTileHeight, tileView.getMeasuredHeight()); + this, mLastQQSTileHeight, tileView.getMeasuredHeight()); } - mOtherTilesExpandAnimator.addView(tileView); + mOtherFirstPageTilesHeightAnimator.addView(tileView); tileView.setClipChildren(true); tileView.setClipToPadding(true); firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1); @@ -392,18 +443,19 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mAllViews.add(tileView); count++; } + if (mCurrentPage != 0) { + addNonFirstPageAnimators(mCurrentPage); + } } if (mAllowFancy) { animateBrightnessSlider(firstPageBuilder); mFirstPageAnimator = firstPageBuilder + // Fade in the tiles/labels as we reach the final position. + .addFloat(tileLayout, "alpha", 0, 1) .setListener(this) .build(); - // Fade in the tiles/labels as we reach the final position. - Builder builder = new Builder() - .addFloat(tileLayout, "alpha", 0, 1); - mFirstPageDelayedAnimator = builder.build(); if (mQQSFooterActions.getVisibility() != View.GONE) { // only when qqs footer is present (which means split shade mode) it needs to @@ -411,9 +463,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha updateQQSFooterAnimation(); } - // Fade in the security footer and the divider as we reach the final position - builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY); + Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY); builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1); if (mQsPanelController.shouldUseHorizontalLayout() && mQsPanelController.mMediaHost.hostView != null) { @@ -425,26 +476,100 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mAllPagesDelayedAnimator = builder.build(); mAllViews.add(mSecurityFooter.getView()); translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); + qqsTranslationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator()); + if (mOnFirstPage) { + // Only recreate this animator if we're in the first page. That way we know that + // the first page is attached and has the proper positions/measures. + mQQSTranslationYAnimator = qqsTranslationYBuilder.build(); + } mTranslationYAnimator = translationYBuilder.build(); mTranslationXAnimator = translationXBuilder.build(); if (mQQSTileHeightAnimator != null) { mQQSTileHeightAnimator.setInterpolator( mQSExpansionPathInterpolator.getYInterpolator()); } - if (mOtherTilesExpandAnimator != null) { - mOtherTilesExpandAnimator.setInterpolator( + if (mOtherFirstPageTilesHeightAnimator != null) { + mOtherFirstPageTilesHeightAnimator.setInterpolator( mQSExpansionPathInterpolator.getYInterpolator()); } } - mNonfirstPageAnimator = new TouchAnimator.Builder() + mNonfirstPageAlphaAnimator = nonFirstPageAlphaBuilder .addFloat(mQuickQsPanel, "alpha", 1, 0) + .addFloat(tileLayout, "alpha", 0, 1) .setListener(mNonFirstPageListener) - .setEndDelay(.5f) + .setEndDelay(1 - QQS_FADE_IN_INTERVAL) .build(); - mNonfirstPageDelayedAnimator = new TouchAnimator.Builder() - .setStartDelay(.14f) - .addFloat(tileLayout, "alpha", 0, 1).build(); + } + + private Pair<HeightExpansionAnimator, TouchAnimator> createSecondaryPageAnimators(int page) { + if (mPagedLayout == null) return null; + HeightExpansionAnimator animator = null; + TouchAnimator.Builder builder = new Builder() + .setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); + TouchAnimator.Builder alphaDelayedBuilder = new Builder() + .setStartDelay(QS_TILE_LABEL_FADE_OUT_START) + .setEndDelay(QS_TILE_LABEL_FADE_OUT_END); + SideLabelTileLayout qqsLayout = (SideLabelTileLayout) mQuickQsPanel.getTileLayout(); + View view = mQs.getView(); + List<String> specs = mPagedLayout.getSpecsForPage(page); + + int row = -1; + int lastTileTop = -1; + + for (int i = 0; i < specs.size(); i++) { + QSTileView tileView = mQsPanelController.getTileView(specs.get(i)); + getRelativePosition(mTmpLoc2, tileView, view); + int diff = mTmpLoc2[1] - (mQQSTop + qqsLayout.getPhantomTopPosition(i)); + builder.addFloat(tileView, "translationY", -diff, 0); + // The different elements in the tile should be centered, so maintain them centered + int centerDiff = (tileView.getMeasuredHeight() - mLastQQSTileHeight) / 2; + builder.addFloat(tileView.getIcon(), "translationY", -centerDiff, 0); + builder.addFloat(tileView.getSecondaryIcon(), "translationY", -centerDiff, 0); + // The labels have different apparent size in QQS vs QS (no secondary label), so the + // translation needs to account for that. + int labelDiff = centerDiff - tileView.getSecondaryLabel().getMeasuredHeight() / 2; + builder.addFloat(tileView.getLabelContainer(), "translationY", -labelDiff, 0); + builder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 0.3f, 1); + + alphaDelayedBuilder.addFloat(tileView.getLabelContainer(), "alpha", 0, 1); + alphaDelayedBuilder.addFloat(tileView.getIcon(), "alpha", 0, 1); + alphaDelayedBuilder.addFloat(tileView.getSecondaryIcon(), "alpha", 0, 1); + + final int tileTop = tileView.getTop(); + if (tileTop != lastTileTop) { + row++; + lastTileTop = tileTop; + } + if (i >= mQuickQsPanel.getTileLayout().getNumVisibleTiles() && row >= 2) { + // Fade completely the tiles in rows below the ones that will merge into QQS. + // args is an array of 0s where the length is the current row index (at least third + // row) + final float[] args = new float[row]; + args[args.length - 1] = 1f; + builder.addFloat(tileView, "alpha", args); + } else { + // For all the other rows, fade them a bit + builder.addFloat(tileView, "alpha", 0.6f, 1); + } + + if (animator == null) { + animator = new HeightExpansionAnimator( + this, mLastQQSTileHeight, tileView.getMeasuredHeight()); + animator.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator()); + } + animator.addView(tileView); + + tileView.setClipChildren(true); + tileView.setClipToPadding(true); + mAllViews.add(tileView); + mAllViews.add(tileView.getSecondaryLabel()); + mAllViews.add(tileView.getIcon()); + mAllViews.add(tileView.getSecondaryIcon()); + mAllViews.add(tileView.getLabelContainer()); + } + builder.addFloat(alphaDelayedBuilder.build(), "position", 0, 1); + return new Pair<>(animator, builder.build()); } private void animateBrightnessSlider(Builder firstPageBuilder) { @@ -542,31 +667,37 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } } mLastPosition = position; - if (mOnFirstPage && mAllowFancy) { + if (!mAllowFancy) return; + if (mOnFirstPage) { mQuickQsPanel.setAlpha(1); mFirstPageAnimator.setPosition(position); - mFirstPageDelayedAnimator.setPosition(position); mTranslationYAnimator.setPosition(position); mTranslationXAnimator.setPosition(position); - if (mQQSTileHeightAnimator != null) { - mQQSTileHeightAnimator.setPosition(position); - } - if (mOtherTilesExpandAnimator != null) { - mOtherTilesExpandAnimator.setPosition(position); + if (mOtherFirstPageTilesHeightAnimator != null) { + mOtherFirstPageTilesHeightAnimator.setPosition(position); } } else { - mNonfirstPageAnimator.setPosition(position); - mNonfirstPageDelayedAnimator.setPosition(position); + mNonfirstPageAlphaAnimator.setPosition(position); } - if (mAllowFancy) { - mAllPagesDelayedAnimator.setPosition(position); - if (mBrightnessAnimator != null) { - mBrightnessAnimator.setPosition(position); - } - if (mQQSFooterActionsAnimator != null) { - mQQSFooterActionsAnimator.setPosition(position); + for (int i = 0; i < mNonFirstPageQSAnimators.size(); i++) { + Pair<HeightExpansionAnimator, TouchAnimator> pair = mNonFirstPageQSAnimators.valueAt(i); + if (pair != null) { + pair.first.setPosition(position); + pair.second.setPosition(position); } } + if (mQQSTileHeightAnimator != null) { + mQQSTileHeightAnimator.setPosition(position); + } + mQSTileLayoutTranslatorAnimator.setPosition(position); + mQQSTranslationYAnimator.setPosition(position); + mAllPagesDelayedAnimator.setPosition(position); + if (mBrightnessAnimator != null) { + mBrightnessAnimator.setPosition(position); + } + if (mQQSFooterActionsAnimator != null) { + mQQSFooterActionsAnimator.setPosition(position); + } } @Override @@ -611,8 +742,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if (mQQSTileHeightAnimator != null) { mQQSTileHeightAnimator.resetViewsHeights(); } - if (mOtherTilesExpandAnimator != null) { - mOtherTilesExpandAnimator.resetViewsHeights(); + if (mOtherFirstPageTilesHeightAnimator != null) { + mOtherFirstPageTilesHeightAnimator.resetViewsHeights(); + } + for (int i = 0; i < mNonFirstPageQSAnimators.size(); i++) { + mNonFirstPageQSAnimators.valueAt(i).first.resetViewsHeights(); } final int N2 = mAnimatedQsViews.size(); for (int i = 0; i < N2; i++) { @@ -623,7 +757,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - mExecutor.execute(mUpdateAnimators); + boolean actualChange = + left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom; + if (actualChange) mExecutor.execute(mUpdateAnimators); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index e82e9d284bdd..bb8a1e8e22a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -504,6 +504,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override + public int getHeightDiff() { + return mQSPanelScrollView.getBottom() - mHeader.getBottom() + + mHeader.getPaddingBottom(); + } + + @Override public void setQsExpansion(float expansion, float panelExpansionFraction, float proposedTranslation, float squishinessFraction) { float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; @@ -537,8 +543,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca boolean fullyExpanded = expansion == 1; boolean fullyCollapsed = expansion == 0.0f; - int heightDiff = mQSPanelScrollView.getBottom() - mHeader.getBottom() - + mHeader.getPaddingBottom(); + int heightDiff = getHeightDiff(); float panelTranslationY = translationScaleY * heightDiff; // Let the views animate their contents correctly by giving them the necessary context. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 20c0fdd7de89..38061a85cc5a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -106,6 +107,7 @@ public class QSPanel extends LinearLayout implements Tunable { protected QSTileLayout mTileLayout; private float mSquishinessFraction = 1f; private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>(); + private final Rect mClippingRect = new Rect(); public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -133,8 +135,7 @@ public class QSPanel extends LinearLayout implements Tunable { mHorizontalContentContainer = new RemeasuringLinearLayout(mContext); mHorizontalContentContainer.setOrientation(LinearLayout.VERTICAL); - mHorizontalContentContainer.setClipChildren(true); - mHorizontalContentContainer.setClipToPadding(false); + setHorizontalContentContainerClipping(); LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1); int marginSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_padding); @@ -148,6 +149,23 @@ public class QSPanel extends LinearLayout implements Tunable { } } + protected void setHorizontalContentContainerClipping() { + mHorizontalContentContainer.setClipChildren(true); + mHorizontalContentContainer.setClipToPadding(false); + // Don't clip on the top, that way, secondary pages tiles can animate up + mHorizontalContentContainer.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (left != oldLeft || right != oldRight || bottom != oldBottom) { + mClippingRect.left = left; + mClippingRect.right = right; + mClippingRect.bottom = bottom; + mHorizontalContentContainer.setClipBounds(mClippingRect); + } + }); + mClippingRect.top = -1000; + mHorizontalContentContainer.setClipBounds(mClippingRect); + } + /** * Add brightness view above the tile layout. * @@ -558,14 +576,6 @@ public class QSPanel extends LinearLayout implements Tunable { fireScanStateChanged(tileRecord.scanState); } } - - @Override - public void onAnnouncementRequested(CharSequence announcement) { - if (announcement != null) { - mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement) - .sendToTarget(); - } - } }; tileRecord.tile.addCallback(callback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index d470fa242f71..ff9790c3595f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -45,6 +45,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; +import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -258,6 +259,15 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr return null; } + QSTileView getTileView(String spec) { + for (QSPanelControllerBase.TileRecord r : mRecords) { + if (Objects.equals(r.tile.getTileSpec(), spec)) { + return r.tileView; + } + } + return null; + } + private String getTilesSpecs() { return mRecords.stream() .map(tileRecord -> tileRecord.tile.getTileSpec()) diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index 613e7f87371c..f5ae019c20ab 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -46,11 +46,9 @@ public class QuickQSPanel extends QSPanel { } @Override - void initialize() { - super.initialize(); - if (mHorizontalContentContainer != null) { - mHorizontalContentContainer.setClipChildren(false); - } + protected void setHorizontalContentContainerClipping() { + mHorizontalContentContainer.setClipToPadding(false); + mHorizontalContentContainer.setClipChildren(false); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index d4350f16873b..eae256532c54 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -212,9 +212,6 @@ public class TileQueryHelper { @Override public void onScanStateChanged(boolean state) {} - - @Override - public void onAnnouncementRequested(CharSequence announcement) {} } private void addPackageTiles(final QSTileHost host) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index c2a9e3adb0b8..76950d1448d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -32,6 +32,7 @@ import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CameraToggleTile; import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; +import com.android.systemui.qs.tiles.ColorCorrectionTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.DataSaverTile; import com.android.systemui.qs.tiles.DeviceControlsTile; @@ -71,6 +72,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider<BluetoothTile> mBluetoothTileProvider; private final Provider<CellularTile> mCellularTileProvider; private final Provider<DndTile> mDndTileProvider; + private final Provider<ColorCorrectionTile> mColorCorrectionTileProvider; private final Provider<ColorInversionTile> mColorInversionTileProvider; private final Provider<AirplaneModeTile> mAirplaneModeTileProvider; private final Provider<WorkModeTile> mWorkModeTileProvider; @@ -133,7 +135,8 @@ public class QSFactoryImpl implements QSFactory { Provider<QuickAccessWalletTile> quickAccessWalletTileProvider, Provider<QRCodeScannerTile> qrCodeScannerTileProvider, Provider<OneHandedModeTile> oneHandedModeTileProvider, - Provider<FgsManagerTile> fgsManagerTileProvider) { + Provider<FgsManagerTile> fgsManagerTileProvider, + Provider<ColorCorrectionTile> colorCorrectionTileProvider) { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; @@ -167,6 +170,7 @@ public class QSFactoryImpl implements QSFactory { mQRCodeScannerTileProvider = qrCodeScannerTileProvider; mOneHandedModeTileProvider = oneHandedModeTileProvider; mFgsManagerTileProvider = fgsManagerTileProvider; + mColorCorrectionTileProvider = colorCorrectionTileProvider; } public final QSTile createTile(String tileSpec) { @@ -239,6 +243,8 @@ public class QSFactoryImpl implements QSFactory { return mOneHandedModeTileProvider.get(); case "fgsmanager": return mFgsManagerTileProvider.get(); + case "color_correction": + return mColorCorrectionTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 70e3a2b2bf26..b5f07d175274 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -156,8 +156,14 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy * * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent} * by editing frameworks/base/proto/src/metrics_constants.proto. + * + * @deprecated Not needed as this logging is deprecated. Logging tiles is done using + * {@link QSTile#getMetricsSpec} */ - abstract public int getMetricsCategory(); + @Deprecated + public int getMetricsCategory() { + return 0; + } /** * Performs initialization of the tile @@ -445,27 +451,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } private void handleStateChanged() { - boolean delayAnnouncement = shouldAnnouncementBeDelayed(); if (mCallbacks.size() != 0) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onStateChanged(mState); } - if (mAnnounceNextStateChange && !delayAnnouncement) { - String announcement = composeChangeAnnouncement(); - if (announcement != null) { - mCallbacks.get(0).onAnnouncementRequested(announcement); - } - } } - mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement; - } - - protected boolean shouldAnnouncementBeDelayed() { - return false; - } - - protected String composeChangeAnnouncement() { - return null; } private void handleShowDetail(boolean show) { 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 6bb7986724b9..7efb983cc4ba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -653,7 +653,8 @@ internal object SubtitleArrayMapping { "qr_code_scanner" to R.array.tile_states_qr_code_scanner, "alarm" to R.array.tile_states_alarm, "onehanded" to R.array.tile_states_onehanded, - "fgsmanager" to R.array.tile_states_fgsmanager + "fgsmanager" to R.array.tile_states_fgsmanager, + "color_correction" to R.array.tile_states_color_correction ) fun getSubtitleId(spec: String?): Int { @@ -665,4 +666,4 @@ private fun colorValuesHolder(name: String, vararg values: Int): PropertyValuesH return PropertyValuesHolder.ofInt(name, *values).apply { setEvaluator(ArgbEvaluator.getInstance()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 5650a17e703a..ccec80db3ea5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -144,15 +144,6 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { return MetricsEvent.QS_AIRPLANEMODE; } - @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_off); - } - } - public void handleSetListening(boolean listening) { super.handleSetListening(listening); if (mListening == listening) return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 6fcfc3af9c87..5552105a77c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -243,15 +243,6 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_off); - } - } - - @Override public boolean isAvailable() { return mController.isBluetoothSupported(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 6e0ae29630bc..76c84f9b1b9e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -287,15 +287,6 @@ public class CastTile extends QSTileImpl<BooleanState> { return MetricsEvent.QS_CAST; } - @Override - protected String composeChangeAnnouncement() { - if (!mState.value) { - // We only announce when it's turned off to avoid vocal overflow. - return mContext.getString(R.string.accessibility_casting_turned_off); - } - return null; - } - private String getDeviceName(CastDevice device) { return device.name != null ? device.name : mContext.getString(R.string.quick_settings_cast_device_default_name); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java new file mode 100644 index 000000000000..6dfcf5ccc258 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.provider.Settings.Secure; +import android.service.quicksettings.Tile; +import android.view.View; +import android.widget.Switch; + +import androidx.annotation.Nullable; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R; +import com.android.systemui.R.drawable; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.QSTile.BooleanState; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.settings.SecureSettings; + +import javax.inject.Inject; + +/** Quick settings tile: Color correction **/ +public class ColorCorrectionTile extends QSTileImpl<BooleanState> { + + private final Icon mIcon = ResourceIcon.get(drawable.ic_qs_color_correction); + private final SettingObserver mSetting; + + @Inject + public ColorCorrectionTile( + QSHost host, + @Background Looper backgroundLooper, + @Main Handler mainHandler, + FalsingManager falsingManager, + MetricsLogger metricsLogger, + StatusBarStateController statusBarStateController, + ActivityStarter activityStarter, + QSLogger qsLogger, + UserTracker userTracker, + SecureSettings secureSettings + ) { + super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger); + + mSetting = new SettingObserver(secureSettings, mHandler, + Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, userTracker.getUserId()) { + @Override + protected void handleValueChanged(int value, boolean observedChange) { + // mHandler is the background handler so calling this is OK + handleRefreshState(value); + } + }; + } + + @Override + protected void handleDestroy() { + super.handleDestroy(); + mSetting.setListening(false); + } + + @Override + public BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void handleSetListening(boolean listening) { + super.handleSetListening(listening); + mSetting.setListening(listening); + } + + @Override + protected void handleUserSwitch(int newUserId) { + mSetting.setUserId(newUserId); + handleRefreshState(mSetting.getValue()); + } + + @Override + public Intent getLongClickIntent() { + return new Intent(Settings.ACTION_COLOR_CORRECTION_SETTINGS); + } + + @Override + protected void handleClick(@Nullable View view) { + mSetting.setValue(mState.value ? 0 : 1); + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.quick_settings_color_correction_label); + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue(); + final boolean enabled = value != 0; + state.value = enabled; + state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.label = mContext.getString(R.string.quick_settings_color_correction_label); + state.icon = mIcon; + state.expandedAccessibilityClassName = Switch.class.getName(); + state.contentDescription = state.label; + } + + @Override + public int getMetricsCategory() { + // MetricsProto/MetricsEvent is deprecated, so just simply return 0 here. + return 0; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index a2577d6e7f60..1bbe411eee25 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -138,15 +138,6 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements } @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_data_saver_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_data_saver_changed_off); - } - } - - @Override public void onDataSaverChanged(boolean isDataSaving) { refreshState(isDataSaving); } 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 bb274588e1e2..49c548da63f5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -307,15 +307,6 @@ public class DndTile extends QSTileImpl<BooleanState> { } @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_off); - } - } - - @Override public void handleSetListening(boolean listening) { super.handleSetListening(listening); if (mListening == listening) return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index c0065a0ec371..73b0896b06a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -153,15 +153,6 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_flashlight_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_flashlight_changed_off); - } - } - - @Override public void onFlashlightChanged(boolean enabled) { refreshState(enabled); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 87edc2cf8bac..d7aa8b21a150 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -195,15 +195,6 @@ public class HotspotTile extends QSTileImpl<BooleanState> { return MetricsEvent.QS_HOTSPOT; } - @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_hotspot_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_hotspot_changed_off); - } - } - /** * Listens to changes made to hotspot and data saver states (to toggle tile availability). */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index 6a018f61a733..fc93f44a44aa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -132,15 +132,6 @@ public class LocationTile extends QSTileImpl<BooleanState> { return MetricsEvent.QS_LOCATION; } - @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_location_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_location_changed_off); - } - } - private final class Callback implements LocationChangeCallback, KeyguardStateController.Callback { @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java index cd2e27a41f1e..0886b46f9d2f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java @@ -147,15 +147,6 @@ public class NfcTile extends QSTileImpl<BooleanState> { return MetricsEvent.QS_NFC; } - @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.quick_settings_nfc_on); - } else { - return mContext.getString(R.string.quick_settings_nfc_off); - } - } - private NfcAdapter getAdapter() { if (mAdapter == null) { try { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 0bbb5bdd851a..177c82ed3d78 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -16,12 +16,18 @@ package com.android.systemui.qs.tiles; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; + +import static com.android.systemui.statusbar.policy.RotationLockControllerImpl.hasSufficientPermission; + import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.os.Handler; import android.os.Looper; import android.provider.Settings; +import android.provider.Settings.Secure; import android.service.quicksettings.Tile; import android.view.View; import android.widget.Switch; @@ -38,18 +44,25 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; +import com.android.systemui.util.settings.SecureSettings; import javax.inject.Inject; /** Quick settings tile: Rotation **/ -public class RotationLockTile extends QSTileImpl<BooleanState> { +public class RotationLockTile extends QSTileImpl<BooleanState> implements + BatteryController.BatteryStateChangeCallback { private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate); private final RotationLockController mController; + private final SensorPrivacyManager mPrivacyManager; + private final BatteryController mBatteryController; + private final SettingObserver mSetting; @Inject public RotationLockTile( @@ -61,12 +74,41 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - RotationLockController rotationLockController + RotationLockController rotationLockController, + SensorPrivacyManager privacyManager, + BatteryController batteryController, + SecureSettings secureSettings ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = rotationLockController; mController.observe(this, mCallback); + mPrivacyManager = privacyManager; + mBatteryController = batteryController; + int currentUser = host.getUserContext().getUserId(); + mSetting = new SettingObserver( + secureSettings, + mHandler, + Secure.CAMERA_AUTOROTATE, + currentUser + ) { + @Override + protected void handleValueChanged(int value, boolean observedChange) { + // mHandler is the background handler so calling this is OK + handleRefreshState(null); + } + }; + mBatteryController.observe(getLifecycle(), this); + } + + @Override + protected void handleInitialize() { + mPrivacyManager.addSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener); + } + + @Override + public void onPowerSaveChanged(boolean isPowerSave) { + refreshState(); } @Override @@ -95,14 +137,46 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { protected void handleUpdateState(BooleanState state, Object arg) { final boolean rotationLocked = mController.isRotationLocked(); + final boolean powerSave = mBatteryController.isPowerSave(); + final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(CAMERA); + final boolean cameraRotation = + !powerSave && !cameraLocked && hasSufficientPermission(mContext) + && mController.isCameraRotationEnabled(); state.value = !rotationLocked; state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label); state.icon = mIcon; state.contentDescription = getAccessibilityString(rotationLocked); + if (!rotationLocked && cameraRotation) { + state.secondaryLabel = mContext.getResources().getString( + R.string.rotation_lock_camera_rotation_on); + } else { + state.secondaryLabel = ""; + } + state.stateDescription = state.secondaryLabel; + state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } + @Override + protected void handleDestroy() { + super.handleDestroy(); + mSetting.setListening(false); + mPrivacyManager.removeSensorPrivacyListener(CAMERA, mSensorPrivacyChangedListener); + } + + @Override + public void handleSetListening(boolean listening) { + super.handleSetListening(listening); + mSetting.setListening(listening); + } + + @Override + protected void handleUserSwitch(int newUserId) { + mSetting.setUserId(newUserId); + handleRefreshState(null); + } + public static boolean isCurrentOrientationLockPortrait(RotationLockController controller, Resources resources) { int lockOrientation = controller.getRotationLockOrientation(); @@ -129,15 +203,14 @@ public class RotationLockTile extends QSTileImpl<BooleanState> { return mContext.getString(R.string.accessibility_quick_settings_rotation); } - @Override - protected String composeChangeAnnouncement() { - return getAccessibilityString(mState.value); - } - private final RotationLockControllerCallback mCallback = new RotationLockControllerCallback() { @Override public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) { refreshState(rotationLocked); } }; + + private final SensorPrivacyManager.OnSensorPrivacyChangedListener + mSensorPrivacyChangedListener = + (sensor, enabled) -> refreshState(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index da0069f29b1c..1608248f7c54 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -254,20 +254,6 @@ public class WifiTile extends QSTileImpl<SignalState> { } @Override - protected boolean shouldAnnouncementBeDelayed() { - return mStateBeforeClick.value == mState.value; - } - - @Override - protected String composeChangeAnnouncement() { - if (mState.value) { - return mContext.getString(R.string.accessibility_quick_settings_wifi_changed_on); - } else { - return mContext.getString(R.string.accessibility_quick_settings_wifi_changed_off); - } - } - - @Override public boolean isAvailable() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index c9c1a9b55c3f..a22fda7f9a47 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -93,6 +93,12 @@ public class BrightnessDialog extends Activity { } @Override + protected void onPause() { + super.onPause(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + } + + @Override protected void onStop() { super.onStop(); MetricsLogger.hidden(this, MetricsEvent.BRIGHTNESS_DIALOG); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index 43b3fb10a920..7cf0583206e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -257,6 +257,30 @@ public abstract class AlertingNotificationManager implements NotificationLifetim return !canRemoveImmediately(entry.getKey()); } + /** + * @param key + * @return true if the entry is pinned + */ + public boolean isSticky(String key) { + AlertEntry alerting = mAlertEntries.get(key); + if (alerting != null) { + return alerting.isSticky(); + } + return false; + } + + /** + * @param key + * @return When a HUN entry should be removed in milliseconds from now + */ + public long getEarliestRemovalTime(String key) { + AlertEntry alerting = mAlertEntries.get(key); + if (alerting != null) { + return Math.max(0, alerting.mEarliestRemovaltime - mClock.currentTimeMillis()); + } + return 0; + } + @Override public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) { if (shouldExtend) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index d7b4738340e6..8ce73d777087 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -31,6 +31,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED; +import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; import android.animation.Animator; @@ -78,6 +79,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; +import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -172,6 +174,18 @@ public class KeyguardIndicationController { return view == mIndicationArea; } }; + private ScreenLifecycle mScreenLifecycle; + private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { + @Override + public void onScreenTurnedOn() { + if (mMessageToShowOnScreenOn != null) { + showBiometricMessage(mMessageToShowOnScreenOn); + // We want to keep this message around in case the screen was off + hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); + mMessageToShowOnScreenOn = null; + } + } + }; /** * Creates a new KeyguardIndicationController and registers callbacks. @@ -190,6 +204,7 @@ public class KeyguardIndicationController { @Main DelayableExecutor executor, FalsingManager falsingManager, LockPatternUtils lockPatternUtils, + ScreenLifecycle screenLifecycle, IActivityManager iActivityManager, KeyguardBypassController keyguardBypassController) { mContext = context; @@ -208,7 +223,8 @@ public class KeyguardIndicationController { mIActivityManager = iActivityManager; mFalsingManager = falsingManager; mKeyguardBypassController = keyguardBypassController; - + mScreenLifecycle = screenLifecycle; + mScreenLifecycle.addObserver(mScreenObserver); } /** Call this after construction to finish setting up the instance. */ @@ -991,7 +1007,7 @@ public class KeyguardIndicationController { if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(helpString, mInitialTextColorState); - } else if (mKeyguardUpdateMonitor.isScreenOn()) { + } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { if (biometricSourceType == BiometricSourceType.FACE && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) { showTryFingerprintMsg(msgId, helpString); @@ -1013,7 +1029,7 @@ public class KeyguardIndicationController { if (biometricSourceType == BiometricSourceType.FACE && shouldSuppressFaceMsgAndShowTryFingerprintMsg() && !mStatusBarKeyguardViewManager.isBouncerShowing() - && mKeyguardUpdateMonitor.isScreenOn()) { + && mScreenLifecycle.getScreenState() == SCREEN_ON) { showTryFingerprintMsg(msgId, errString); return; } @@ -1035,7 +1051,7 @@ public class KeyguardIndicationController { } } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState); - } else if (mKeyguardUpdateMonitor.isScreenOn()) { + } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { showBiometricMessage(errString); } else { mMessageToShowOnScreenOn = errString; @@ -1086,16 +1102,6 @@ public class KeyguardIndicationController { } @Override - public void onScreenTurnedOn() { - if (mMessageToShowOnScreenOn != null) { - showBiometricMessage(mMessageToShowOnScreenOn); - // We want to keep this message around in case the screen was off - hideBiometricMessageDelayed(HIDE_DELAY_MS); - mMessageToShowOnScreenOn = null; - } - } - - @Override public void onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType) { if (running && biometricSourceType == BiometricSourceType.FACE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 210ee96da7c0..3730d123021b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -379,6 +379,7 @@ public class NotificationMediaManager implements Dumpable { } } + @Nullable public String getMediaNotificationKey() { return mMediaNotificationKey; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 2dbe59e27ea6..bd948eceab9c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -28,6 +28,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.os.SystemProperties; +import android.os.Trace; import android.text.format.DateFormat; import android.util.FloatProperty; import android.util.Log; @@ -193,6 +194,7 @@ public class StatusBarStateControllerImpl implements mState = state; mUpcomingState = state; mUiEventLogger.log(StatusBarStateEvent.fromState(mState)); + Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "StatusBarState " + tag); for (RankedListener rl : new ArrayList<>(mListeners)) { rl.mListener.onStateChanged(mState); } 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 f500d39032ba..4717b3afb66d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -20,8 +20,6 @@ import static android.service.notification.NotificationListenerService.REASON_ER import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.Notification; import android.os.RemoteException; import android.os.SystemClock; @@ -33,6 +31,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; @@ -123,8 +124,8 @@ public class NotificationEntryManager implements * filtered out if for instance they are not for the current user */ private final ArrayMap<String, NotificationEntry> mActiveNotifications = new ArrayMap<>(); - @VisibleForTesting /** This is the list of "active notifications for this user in this context" */ + @VisibleForTesting protected final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>(); private final List<NotificationEntry> mReadOnlyNotifications = Collections.unmodifiableList(mSortedAndFiltered); @@ -899,7 +900,7 @@ public class NotificationEntryManager implements } /** Calls to NotificationRankingManager and updates mSortedAndFiltered */ - private void updateRankingAndSort(@NonNull RankingMap rankingMap, String reason) { + private void updateRankingAndSort(RankingMap rankingMap, String reason) { if (mNotifPipelineFlags.isNewPipelineEnabled()) { mLogger.logUseWhileNewPipelineActive("updateRankingAndSort", reason); return; @@ -961,6 +962,7 @@ public class NotificationEntryManager implements * Returns a collections containing ALL notifications we know about, including ones that are * hidden or for other users. See {@link CommonNotifCollection#getAllNotifs()}. */ + @NonNull @Override public Collection<NotificationEntry> getAllNotifs() { mNotifPipelineFlags.checkLegacyPipelineEnabled(); @@ -969,7 +971,7 @@ public class NotificationEntryManager implements @Nullable @Override - public NotificationEntry getEntry(String key) { + public NotificationEntry getEntry(@NonNull String key) { mNotifPipelineFlags.checkLegacyPipelineEnabled(); return getPendingOrActiveNotif(key); } @@ -989,7 +991,7 @@ public class NotificationEntryManager implements } @Override - public void addCollectionListener(NotifCollectionListener listener) { + public void addCollectionListener(@NonNull NotifCollectionListener listener) { mNotifCollectionListeners.add(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index 22c3eda03b1e..9da7d21886ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -16,7 +16,8 @@ import kotlin.math.max class NotificationLaunchAnimatorControllerProvider( private val notificationShadeWindowViewController: NotificationShadeWindowViewController, private val notificationListContainer: NotificationListContainer, - private val headsUpManager: HeadsUpManagerPhone + private val headsUpManager: HeadsUpManagerPhone, + private val jankMonitor: InteractionJankMonitor ) { fun getAnimatorController( notification: ExpandableNotificationRow @@ -25,7 +26,8 @@ class NotificationLaunchAnimatorControllerProvider( notificationShadeWindowViewController, notificationListContainer, headsUpManager, - notification + notification, + jankMonitor ) } } @@ -39,7 +41,8 @@ class NotificationLaunchAnimatorController( private val notificationShadeWindowViewController: NotificationShadeWindowViewController, private val notificationListContainer: NotificationListContainer, private val headsUpManager: HeadsUpManagerPhone, - private val notification: ExpandableNotificationRow + private val notification: ExpandableNotificationRow, + private val jankMonitor: InteractionJankMonitor ) : ActivityLaunchAnimator.Controller { companion object { @@ -137,12 +140,12 @@ class NotificationLaunchAnimatorController( notification.isExpandAnimationRunning = true notificationListContainer.setExpandingNotification(notification) - InteractionJankMonitor.getInstance().begin(notification, + jankMonitor.begin(notification, InteractionJankMonitor.CUJ_NOTIFICATION_APP_START) } override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { - InteractionJankMonitor.getInstance().end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START) + jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START) notification.isExpandAnimationRunning = false notificationShadeWindowViewController.setExpandAnimationRunning(false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index f8f1279044d5..b6b9c3f1cf9d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -44,7 +44,6 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.MainThread; -import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Notification; import android.os.Handler; @@ -59,6 +58,7 @@ import android.util.ArrayMap; import android.util.Pair; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.Dumpable; @@ -193,7 +193,8 @@ public class NotifCollection implements Dumpable { } /** @see NotifPipeline#getEntry(String) () */ - NotificationEntry getEntry(String key) { + @Nullable + NotificationEntry getEntry(@NonNull String key) { return mNotificationSet.get(key); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java index e9b7caa565ac..85c0064e7983 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java @@ -82,11 +82,8 @@ public class BubbleCoordinator implements Coordinator { public void attach(NotifPipeline pipeline) { mNotifPipeline = pipeline; mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor); - mNotifPipeline.addFinalizeFilter(mNotifFilter); - if (mBubblesManagerOptional.isPresent()) { - mBubblesManagerOptional.get().addNotifCallback(mNotifCallback); - } - + mNotifPipeline.addPreGroupFilter(mNotifFilter); + mBubblesManagerOptional.ifPresent(manager -> manager.addNotifCallback(mNotifCallback)); } private final NotifFilter mNotifFilter = new NotifFilter(TAG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java index f8b4274188f5..ed9663e9c672 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java @@ -19,9 +19,12 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY; import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain; +import android.util.ArraySet; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -38,8 +41,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; - -import java.util.Objects; +import com.android.systemui.util.concurrency.DelayableExecutor; import javax.inject.Inject; @@ -66,12 +68,11 @@ public class HeadsUpCoordinator implements Coordinator { private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final NotificationRemoteInputManager mRemoteInputManager; private final NodeController mIncomingHeaderController; - - // tracks the current HeadUpNotification reported by HeadsUpManager - private @Nullable NotificationEntry mCurrentHun; + private final DelayableExecutor mExecutor; private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension; - private NotificationEntry mNotifExtendingLifetime; // notif we've extended the lifetime for + // notifs we've extended the lifetime for + private final ArraySet<NotificationEntry> mNotifsExtendingLifetime = new ArraySet<>(); @Inject public HeadsUpCoordinator( @@ -79,12 +80,14 @@ public class HeadsUpCoordinator implements Coordinator { HeadsUpViewBinder headsUpViewBinder, NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationRemoteInputManager remoteInputManager, - @IncomingHeader NodeController incomingHeaderController) { + @IncomingHeader NodeController incomingHeaderController, + @Main DelayableExecutor executor) { mHeadsUpManager = headsUpManager; mHeadsUpViewBinder = headsUpViewBinder; mNotificationInterruptStateProvider = notificationInterruptStateProvider; mRemoteInputManager = remoteInputManager; mIncomingHeaderController = incomingHeaderController; + mExecutor = executor; } @Override @@ -178,16 +181,26 @@ public class HeadsUpCoordinator implements Coordinator { public boolean shouldExtendLifetime(@NonNull NotificationEntry entry, int reason) { boolean isShowingHun = isCurrentlyShowingHun(entry); if (isShowingHun) { - mNotifExtendingLifetime = entry; + if (isSticky(entry)) { + long removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.getKey()); + if (removeAfterMillis <= 0) return false; + mExecutor.executeDelayed(() -> { + // make sure that the entry was not updated + long removeAfterMillis2 = + mHeadsUpManager.getEarliestRemovalTime(entry.getKey()); + if (mNotifsExtendingLifetime.contains(entry) && removeAfterMillis2 <= 0) { + mHeadsUpManager.removeNotification(entry.getKey(), true); + } + }, removeAfterMillis); + } + mNotifsExtendingLifetime.add(entry); } return isShowingHun; } @Override public void cancelLifetimeExtension(@NonNull NotificationEntry entry) { - if (Objects.equals(mNotifExtendingLifetime, entry)) { - mNotifExtendingLifetime = null; - } + mNotifsExtendingLifetime.remove(entry); } }; @@ -220,27 +233,24 @@ public class HeadsUpCoordinator implements Coordinator { new OnHeadsUpChangedListener() { @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { - NotificationEntry newHUN = mHeadsUpManager.getTopEntry(); - if (!Objects.equals(mCurrentHun, newHUN)) { - mCurrentHun = newHUN; - endNotifLifetimeExtension(); - } if (!isHeadsUp) { mHeadsUpViewBinder.unbindHeadsUpView(entry); + endNotifLifetimeExtensionIfExtended(entry); } } }; + private boolean isSticky(NotificationEntry entry) { + return mHeadsUpManager.isSticky(entry.getKey()); + } + private boolean isCurrentlyShowingHun(ListEntry entry) { - return mCurrentHun == entry.getRepresentativeEntry(); + return mHeadsUpManager.isAlerting(entry.getKey()); } - private void endNotifLifetimeExtension() { - if (mNotifExtendingLifetime != null) { - mEndLifetimeExtension.onEndLifetimeExtension( - mLifetimeExtender, - mNotifExtendingLifetime); - mNotifExtendingLifetime = null; + private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) { + if (mNotifsExtendingLifetime.remove(entry)) { + mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index fe1cd7b98cf9..33005b34ff98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -90,6 +90,7 @@ public class KeyguardCoordinator implements Coordinator { readShowSilentNotificationSetting(); setupInvalidateNotifListCallbacks(); + // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator pipeline.addFinalizeFilter(mNotifFilter); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java index 8769969834c8..ecee00641cd3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java @@ -49,6 +49,6 @@ public class MediaCoordinator implements Coordinator { @Override public void attach(NotifPipeline pipeline) { - pipeline.addFinalizeFilter(mMediaFilter); + pipeline.addPreGroupFilter(mMediaFilter); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java index 471c3571418e..beaa1ba52ddf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -37,7 +38,7 @@ public interface CommonNotifCollection { * Registers a listener to be informed when notifications are created, added, updated, removed, * or deleted. */ - void addCollectionListener(NotifCollectionListener listener); + void addCollectionListener(@NonNull NotifCollectionListener listener); /** * Returns the list of all known notifications, i.e. the notifications that are currently posted @@ -46,11 +47,11 @@ public interface CommonNotifCollection { * * The returned collection is read-only, unsorted, unfiltered, and ungrouped. */ - Collection<NotificationEntry> getAllNotifs(); + @NonNull Collection<NotificationEntry> getAllNotifs(); /** * Returns the notification entry for the given notification key; * the returned entry (if present) may be in any state. */ - @Nullable NotificationEntry getEntry(String key); + @Nullable NotificationEntry getEntry(@NonNull String key); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt index b2e15f48004c..b61a5408626b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt @@ -77,11 +77,11 @@ class BypassHeadsUpNotifier @Inject constructor( override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) { val previous = currentMediaEntry - var newEntry = commonNotifCollection.getEntry(mediaManager.mediaNotificationKey) - if (!NotificationMediaManager.isPlayingState(state)) { - newEntry = null - } - currentMediaEntry = newEntry + val mediaNotificationKey = mediaManager.mediaNotificationKey + currentMediaEntry = + if (mediaNotificationKey != null && NotificationMediaManager.isPlayingState(state)) + commonNotifCollection.getEntry(mediaNotificationKey) + else null updateAutoHeadsUp(previous) updateAutoHeadsUp(currentMediaEntry) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 4b3d6f76ba9d..6218c7708020 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -23,8 +23,10 @@ import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.IndentingPrintWriter; +import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -517,6 +519,76 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { return mChangingPosition; } + /** + * Called when removing a view from its transient container, such as at the end of an animation. + * Generally, when operating on ExpandableView instances, this should be used rather than + * {@link ExpandableView#removeTransientView(View)} to ensure that the + * {@link #getTransientContainer() transient container} is correctly reset. + */ + public void removeFromTransientContainer() { + final ViewGroup transientContainer = getTransientContainer(); + if (transientContainer == null) { + return; + } + final ViewParent parent = getParent(); + if (parent != transientContainer) { + Log.w(TAG, "Expandable view " + this + + " has transient container " + transientContainer + + " but different parent " + parent); + setTransientContainer(null); + return; + } + transientContainer.removeTransientView(this); + setTransientContainer(null); + } + + /** + * Called before adding this view to a group, which would always throw an exception if this view + * has a different parent, so clean up the transient container and throw an exception if the + * parent isn't a transient container. Provide as much detail as possible in the crash. + */ + public void removeFromTransientContainerForAdditionTo(ViewGroup newParent) { + final ViewParent parent = getParent(); + final ViewGroup transientContainer = getTransientContainer(); + if (parent == null) { + // If this view has no parent, the add will succeed, so just make sure the tracked + // transient container is in sync with the lack of a parent. + if (transientContainer != null) { + Log.w(TAG, "Expandable view " + this + + " has transient container " + transientContainer + + " but no parent"); + setTransientContainer(null); + } + return; + } + if (transientContainer == null) { + throw new IllegalStateException( + "Can't add view " + this + " to container " + newParent + "; current parent " + + parent + " is not a transient container"); + } + if (transientContainer != parent) { + String transientContainerOutOfSyncError = "Expandable view " + this + + " has transient container " + transientContainer + + " but different parent " + parent; + if (parent != newParent) { + // Crash with details before addView() crashes without any; the view is being added + // to a different parent, and the transient container isn't the parent, so we can't + // even (safely) clean that up. + throw new IllegalStateException(transientContainerOutOfSyncError); + } else { + Log.w(TAG, transientContainerOutOfSyncError); + setTransientContainer(null); + return; + } + } + if (parent != newParent) { + Log.w(TAG, "Moving view " + this + " from transient container " + + transientContainer + " to parent " + newParent); + } + transientContainer.removeTransientView(this); + setTransientContainer(null); + } + public void setTransientContainer(ViewGroup transientContainer) { mTransientContainer = transientContainer; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 3a37fb44b33a..9d599cbe1caf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -41,11 +41,8 @@ import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.AlphaOptimizedImageView; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -304,12 +301,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } else { mMenuContainer = new FrameLayout(mContext); } - // The setting can win (which is needed for tests) but if not set, then use the flag final int showDismissSetting = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1); - final boolean newFlowHideShelf = showDismissSetting == -1 - ? Dependency.get(FeatureFlags.class).isEnabled(Flags.NOTIFICATION_UPDATES) - : showDismissSetting == 1; + Settings.Global.SHOW_NEW_NOTIF_DISMISS, /* default = */ 1); + final boolean newFlowHideShelf = showDismissSetting == 1; if (newFlowHideShelf) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 046a133741ae..a3fe47cb1f07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -44,6 +44,7 @@ import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.HybridGroupManager; import com.android.systemui.statusbar.notification.row.HybridNotificationView; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; @@ -272,6 +273,7 @@ public class NotificationChildrenContainer extends ViewGroup * @param childIndex the index to add it at, if -1 it will be added at the end */ public void addNotification(ExpandableNotificationRow row, int childIndex) { + ensureRemovedFromTransientContainer(row); int newIndex = childIndex < 0 ? mAttachedChildren.size() : childIndex; mAttachedChildren.add(newIndex, row); addView(row); @@ -291,6 +293,16 @@ public class NotificationChildrenContainer extends ViewGroup } } + private void ensureRemovedFromTransientContainer(View v) { + if (v.getParent() != null && v instanceof ExpandableView) { + // If the child is animating away, it will still have a parent, so detach it first + // TODO: We should really cancel the active animations here. This will + // happen automatically when the view's intro animation starts, but + // it's a fragile link. + ((ExpandableView) v).removeFromTransientContainerForAdditionTo(this); + } + } + public void removeNotification(ExpandableNotificationRow row) { int childIndex = mAttachedChildren.indexOf(row); mAttachedChildren.remove(row); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 1cb5e6267fbf..464fd06587e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -21,8 +21,6 @@ import android.util.MathUtils; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -41,7 +39,6 @@ public class NotificationRoundnessManager { private final ExpandableView[] mLastInSectionViews; private final ExpandableView[] mTmpFirstInSectionViews; private final ExpandableView[] mTmpLastInSectionViews; - private final FeatureFlags mFeatureFlags; private boolean mExpanded; private HashSet<ExpandableView> mAnimatedChildren; private Runnable mRoundingChangedCallback; @@ -56,9 +53,7 @@ public class NotificationRoundnessManager { @Inject NotificationRoundnessManager( - NotificationSectionsFeatureManager sectionsFeatureManager, - FeatureFlags featureFlags) { - mFeatureFlags = featureFlags; + NotificationSectionsFeatureManager sectionsFeatureManager) { int numberOfSections = sectionsFeatureManager.getNumberOfBuckets(); mFirstInSectionViews = new ExpandableView[numberOfSections]; mLastInSectionViews = new ExpandableView[numberOfSections]; @@ -125,9 +120,6 @@ public class NotificationRoundnessManager { ExpandableView viewBefore, ExpandableView viewSwiped, ExpandableView viewAfter) { - if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_UPDATES)) { - return; - } final boolean animate = true; ExpandableView oldViewBefore = mViewBeforeSwipedView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index 88198f394683..1d90780ffee8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -215,8 +215,7 @@ class NotificationSectionsManager @Inject internal constructor( // TODO: We should really cancel the active animations here. This will // happen automatically when the view's intro animation starts, but // it's a fragile link. - header.transientContainer?.removeTransientView(header) - header.transientContainer = null + header.removeFromTransientContainer() parent.addView(header, target) } else { parent.changeViewPosition(header, target) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 79b05c910705..943f05fe15ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -43,7 +43,6 @@ import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; -import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.AttributeSet; @@ -80,6 +79,8 @@ import com.android.systemui.Dumpable; import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.EmptyShadeView; @@ -132,14 +133,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; - // Usage: - // adb shell setprop persist.debug.nssl true && adb reboot - private static final boolean DEBUG = SystemProperties.getBoolean("persist.debug.nssl", - false /* default */); - // TODO(b/187291379) disable again before release - private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean( - "persist.debug.nssl.dismiss", false /* default */); - // Delay in milli-seconds before shade closes for clear all. private final int DELAY_BEFORE_SHADE_CLOSE = 200; private boolean mShadeNeedsToClose = false; @@ -192,7 +185,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private float mInitialTouchX; private float mInitialTouchY; + private final boolean mDebugLines; private Paint mDebugPaint; + /** Used to track the Y positions that were already used to draw debug text labels. */ + private Set<Integer> mDebugTextUsedYPositions; + private final boolean mDebugRemoveAnimation; + private int mContentHeight; private int mIntrinsicContentHeight; private int mCollapsedSize; @@ -569,6 +567,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public NotificationStackScrollLayout(Context context, AttributeSet attrs) { super(context, attrs, 0, 0); Resources res = getResources(); + FeatureFlags featureFlags = Dependency.get(FeatureFlags.class); + mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); + mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -591,10 +592,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable res.getBoolean(R.bool.config_drawNotificationBackground); setOutlineProvider(mOutlineProvider); - boolean willDraw = mShouldDrawNotificationBackground || DEBUG; + boolean willDraw = mShouldDrawNotificationBackground || mDebugLines; setWillNotDraw(!willDraw); mBackgroundPaint.setAntiAlias(true); - if (DEBUG) { + if (mDebugLines) { mDebugPaint = new Paint(); mDebugPaint.setColor(0xffff0000); mDebugPaint.setStrokeWidth(2); @@ -729,18 +730,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable drawHeadsUpBackground(canvas); } - if (DEBUG) { + if (mDebugLines) { onDrawDebug(canvas); } } - /** Used to track the Y positions that were already used to draw debug text labels. */ - private static final Set<Integer> DEBUG_TEXT_USED_Y_POSITIONS = - DEBUG ? new HashSet<>() : Collections.emptySet(); - private void onDrawDebug(Canvas canvas) { - DEBUG_TEXT_USED_Y_POSITIONS.clear(); - + if (mDebugTextUsedYPositions == null) { + mDebugTextUsedYPositions = new HashSet<>(); + } else { + mDebugTextUsedYPositions.clear(); + } int y = mTopPadding; drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding"); @@ -776,10 +776,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int computeDebugYTextPosition(int lineY) { int textY = lineY; - while (DEBUG_TEXT_USED_Y_POSITIONS.contains(textY)) { + while (mDebugTextUsedYPositions.contains(textY)) { textY = (int) (textY + mDebugPaint.getTextSize()); } - DEBUG_TEXT_USED_Y_POSITIONS.add(textY); + mDebugTextUsedYPositions.add(textY); return textY; } @@ -2702,14 +2702,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) boolean generateRemoveAnimation(ExpandableView child) { String key = ""; - if (DEBUG_REMOVE_ANIMATION) { + if (mDebugRemoveAnimation) { if (child instanceof ExpandableNotificationRow) { key = ((ExpandableNotificationRow) child).getEntry().getKey(); } Log.d(TAG, "generateRemoveAnimation " + key); } if (removeRemovedChildFromHeadsUpChangeAnimations(child)) { - if (DEBUG_REMOVE_ANIMATION) { + if (mDebugRemoveAnimation) { Log.d(TAG, "removedBecauseOfHeadsUp " + key); } mAddedHeadsUpChildren.remove(child); @@ -2720,7 +2720,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mClearTransientViewsWhenFinished.add(child); return true; } - if (DEBUG_REMOVE_ANIMATION) { + if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key + "\nmIsExpanded " + mIsExpanded + "\nmAnimationsEnabled " + mAnimationsEnabled @@ -2728,7 +2728,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) { if (!mChildrenToAddAnimated.contains(child)) { - if (DEBUG_REMOVE_ANIMATION) { + if (mDebugRemoveAnimation) { Log.d(TAG, "needsAnimation = true " + key); } // Generate Animations @@ -3213,10 +3213,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // Clean up any potential transient views if the child has already been swiped // out, as we won't be animating it further (due to its height already being // clipped to 0. - ViewGroup transientContainer = child.getTransientContainer(); - if (transientContainer != null) { - transientContainer.removeTransientView(child); - } + child.removeFromTransientContainer(); } } int animationType = childWasSwipedOut @@ -3227,7 +3224,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable ignoreChildren); mAnimationEvents.add(event); mSwipedOutViews.remove(child); - if (DEBUG_REMOVE_ANIMATION) { + if (mDebugRemoveAnimation) { String key = ""; if (child instanceof ExpandableNotificationRow) { key = ((ExpandableNotificationRow) child).getEntry().getKey(); @@ -3933,7 +3930,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearTemporaryViewsInGroup(ViewGroup viewGroup) { while (viewGroup != null && viewGroup.getTransientViewCount() != 0) { - viewGroup.removeTransientView(viewGroup.getTransientView(0)); + final View transientView = viewGroup.getTransientView(0); + viewGroup.removeTransientView(transientView); + if (transientView instanceof ExpandableView) { + ((ExpandableView) transientView).setTransientContainer(null); + } } } @@ -4102,7 +4103,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearTransient() { for (ExpandableView view : mClearTransientViewsWhenFinished) { - StackStateAnimator.removeTransientView(view); + view.removeFromTransientContainer(); } mClearTransientViewsWhenFinished.clear(); } @@ -4655,18 +4656,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void ensureRemovedFromTransientContainer(View v) { - if (v.getParent() == this && v instanceof ExpandableView) { - ExpandableView expandableView = (ExpandableView) v; - ViewGroup transientContainer = expandableView.getTransientContainer(); - // If the child is animating away, it will still have a parent, so - // detach it first + if (v.getParent() != null && v instanceof ExpandableView) { + // If the child is animating away, it will still have a parent, so detach it first // TODO: We should really cancel the active animations here. This will // happen automatically when the view's intro animation starts, but // it's a fragile link. - if (transientContainer != null) { - transientContainer.removeTransientView(v); - expandableView.setTransientContainer(null); - } + ((ExpandableView) v).removeFromTransientContainerForAdditionTo(this); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 41a80c7aceeb..fc612a934b43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -183,6 +183,7 @@ public class NotificationStackScrollLayoutController { private final NotificationGroupManagerLegacy mLegacyGroupManager; private final SectionHeaderController mSilentHeaderController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final InteractionJankMonitor mJankMonitor; private NotificationStackScrollLayout mView; private boolean mFadeNotificationsOnDismiss; @@ -454,10 +455,7 @@ public class NotificationStackScrollLayoutController { if (!row.isDismissed()) { handleChildViewDismissed(view); } - ViewGroup transientContainer = row.getTransientContainer(); - if (transientContainer != null) { - transientContainer.removeTransientView(view); - } + row.removeFromTransientContainer(); } /** @@ -661,7 +659,8 @@ public class NotificationStackScrollLayoutController { LayoutInflater layoutInflater, NotificationRemoteInputManager remoteInputManager, VisualStabilityManager visualStabilityManager, - ShadeController shadeController) { + ShadeController shadeController, + InteractionJankMonitor jankMonitor) { mAllowLongPress = allowLongPress; mNotificationGutsManager = notificationGutsManager; mVisibilityProvider = visibilityProvider; @@ -684,6 +683,7 @@ public class NotificationStackScrollLayoutController { mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder; mStatusBar = statusBar; mScrimController = scrimController; + mJankMonitor = jankMonitor; groupManager.registerGroupExpansionChangeListener( (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() { @@ -1784,9 +1784,9 @@ public class NotificationStackScrollLayoutController { // We log any touches other than down, which will be captured by onTouchEvent. // In the intercept we only start tracing when it's not a down (otherwise that down // would be duplicated when intercepted). - if (scrollWantsIt && ev.getActionMasked() != MotionEvent.ACTION_DOWN) { - InteractionJankMonitor.getInstance().begin(mView, - CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + if (mJankMonitor != null && scrollWantsIt + && ev.getActionMasked() != MotionEvent.ACTION_DOWN) { + mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING); } return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt; } @@ -1854,24 +1854,25 @@ public class NotificationStackScrollLayoutController { } private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) { + if (mJankMonitor == null) { + Log.w(TAG, "traceJankOnTouchEvent, mJankMonitor is null"); + return; + } // Handle interaction jank monitor cases. switch (action) { case MotionEvent.ACTION_DOWN: if (scrollerWantsIt) { - InteractionJankMonitor.getInstance() - .begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING); } break; case MotionEvent.ACTION_UP: if (scrollerWantsIt && !mView.isFlingAfterUpEvent()) { - InteractionJankMonitor.getInstance() - .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + mJankMonitor.end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); } break; case MotionEvent.ACTION_CANCEL: if (scrollerWantsIt) { - InteractionJankMonitor.getInstance() - .cancel(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + mJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); } break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 2dc92764a4af..1d0d3742bcd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -322,9 +322,8 @@ public class StackStateAnimator { private void onAnimationFinished() { mHostLayout.onChildAnimationFinished(); - for (ExpandableView transientViewsToRemove : mTransientViewsToRemove) { - transientViewsToRemove.getTransientContainer() - .removeTransientView(transientViewsToRemove); + for (ExpandableView transientViewToRemove : mTransientViewsToRemove) { + transientViewToRemove.removeFromTransientContainer(); } mTransientViewsToRemove.clear(); } @@ -353,7 +352,7 @@ public class StackStateAnimator { } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { if (changingView.getVisibility() != View.VISIBLE) { - removeTransientView(changingView); + changingView.removeFromTransientContainer(); continue; } @@ -390,12 +389,11 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - 0, () -> removeTransientView(changingView), null); + 0, changingView::removeFromTransientContainer, null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (mHostLayout.isFullySwipedOut(changingView) - && changingView.getTransientContainer() != null) { - changingView.getTransientContainer().removeTransientView(changingView); + if (mHostLayout.isFullySwipedOut(changingView)) { + changingView.removeFromTransientContainer(); } } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { @@ -425,7 +423,7 @@ public class StackStateAnimator { mHostLayout.addTransientView(changingView, 0); changingView.setTransientContainer(mHostLayout); mTmpState.initFrom(changingView); - endRunnable = () -> removeTransientView(changingView); + endRunnable = changingView::removeFromTransientContainer; } float targetLocation = 0; boolean needsAnimation = true; @@ -468,12 +466,6 @@ public class StackStateAnimator { } } - public static void removeTransientView(ExpandableView viewToRemove) { - if (viewToRemove.getTransientContainer() != null) { - viewToRemove.getTransientContainer().removeTransientView(viewToRemove); - } - } - public void animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded) { final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); 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 ad1c23283912..bfa4a245070c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -634,20 +634,22 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp 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 == BiometricSourceType.FINGERPRINT + && mUpdateMonitor.isUdfpsSupported()) { + long currUptimeMillis = SystemClock.uptimeMillis(); + if (currUptimeMillis - mLastFpFailureUptimeMillis + < (mStatusBarStateController.isDozing() ? 3500 : 2000)) { + 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; + if (mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) { + startWakeAndUnlock(MODE_SHOW_BOUNCER); + UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN); + mNumConsecutiveFpFailures = 0; + } } cleanup(); } @@ -668,7 +670,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp && mUpdateMonitor.isUdfpsSupported() && (mStatusBarStateController.getState() == StatusBarState.SHADE || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) { - mKeyguardViewController.showBouncer(true); + startWakeAndUnlock(MODE_SHOW_BOUNCER); + UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN); } cleanup(); } @@ -735,6 +738,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp pw.println(" BiometricUnlockController:"); pw.print(" mMode="); pw.println(mMode); pw.print(" mWakeLock="); pw.println(mWakeLock); + if (mUpdateMonitor.isUdfpsSupported()) { + pw.print(" mNumConsecutiveFpFailures="); pw.println(mNumConsecutiveFpFailures); + pw.print(" time since last failure="); + pw.println(SystemClock.uptimeMillis() - mLastFpFailureUptimeMillis); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index a88a3b6392c2..b35e684203e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -254,7 +254,6 @@ public final class DozeServiceHost implements DozeHost { private void setPulsing(boolean pulsing) { mStatusBarKeyguardViewManager.setPulsing(pulsing); - mKeyguardViewMediator.setPulsing(pulsing); mNotificationPanel.setPulsing(pulsing); mStatusBarStateController.setPulsing(pulsing); mIgnoreTouchWhilePulsing = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt new file mode 100644 index 000000000000..56b0d598a512 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt @@ -0,0 +1,57 @@ +/* + * 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.statusbar.phone + +import android.content.Context +import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback +import com.android.internal.R + +/** + * Listens for fold state changes and reports the new folded state together with other properties + * associated with that state. + */ +internal class FoldStateListener( + context: Context, + private val listener: OnFoldStateChangeListener +) : DeviceStateCallback { + + internal interface OnFoldStateChangeListener { + /** + * Reports that the device either became folded or unfolded. + * + * @param folded whether the device is folded. + * @param willGoToSleep whether the device will go to sleep and keep the screen off. + */ + fun onFoldStateChanged(folded: Boolean, willGoToSleep: Boolean) + } + + private val foldedDeviceStates: IntArray = + context.resources.getIntArray(R.array.config_foldedDeviceStates) + private val goToSleepDeviceStates: IntArray = + context.resources.getIntArray(R.array.config_deviceStatesOnWhichToSleep) + + private var wasFolded: Boolean? = null + + override fun onStateChanged(state: Int) { + val isFolded = foldedDeviceStates.contains(state) + if (wasFolded == isFolded) { + return + } + wasFolded = isFolded + val willGoToSleep = goToSleepDeviceStates.contains(state) + listener.onFoldStateChanged(isFolded, willGoToSleep) + } +} 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 9647486be992..565b2d333d4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -33,7 +33,6 @@ import android.view.WindowInsets; import com.android.internal.policy.SystemBarUtils; import com.android.keyguard.KeyguardHostViewController; -import com.android.keyguard.KeyguardRootViewController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardSecurityView; import com.android.keyguard.KeyguardUpdateMonitor; @@ -42,7 +41,6 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.DejankUtils; import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -105,12 +103,11 @@ public class KeyguardBouncer { private int mStatusBarHeight; private float mExpansion = EXPANSION_HIDDEN; - protected ViewGroup mRoot; - private KeyguardRootViewController mRootViewController; private boolean mShowingSoon; private int mBouncerPromptReason; private boolean mIsAnimatingAway; private boolean mIsScrimmed; + private boolean mInitialized; private KeyguardBouncer(Context context, ViewMediatorCallback callback, ViewGroup container, @@ -184,7 +181,7 @@ public class KeyguardBouncer { showPrimarySecurityScreen(); } - if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) { + if (mContainer.getVisibility() == View.VISIBLE || mShowingSoon) { return; } @@ -235,10 +232,8 @@ public class KeyguardBouncer { Log.wtf(TAG, "onFullyShown when view was null"); } else { mKeyguardViewController.onResume(); - if (mRoot != null) { - mRoot.announceForAccessibility( - mKeyguardViewController.getAccessibilityTitleForCurrentMode()); - } + mContainer.announceForAccessibility( + mKeyguardViewController.getAccessibilityTitleForCurrentMode()); } } @@ -253,10 +248,8 @@ public class KeyguardBouncer { } private void setVisibility(@View.Visibility int visibility) { - if (mRoot != null) { - mRoot.setVisibility(visibility); - dispatchVisibilityChanged(); - } + mContainer.setVisibility(visibility); + dispatchVisibilityChanged(); } private final Runnable mShowRunnable = new Runnable() { @@ -337,14 +330,12 @@ public class KeyguardBouncer { mKeyguardViewController.cleanUp(); } mIsAnimatingAway = false; - if (mRoot != null) { - setVisibility(View.INVISIBLE); - if (destroyView) { + setVisibility(View.INVISIBLE); + if (destroyView) { - // We have a ViewFlipper that unregisters a broadcast when being detached, which may - // be slow because of AM lock contention during unlocking. We can delay it a bit. - mHandler.postDelayed(mRemoveViewRunnable, 50); - } + // We have a ViewFlipper that unregisters a broadcast when being detached, which may + // be slow because of AM lock contention during unlocking. We can delay it a bit. + mHandler.postDelayed(mRemoveViewRunnable, 50); } } @@ -370,14 +361,13 @@ public class KeyguardBouncer { } public void onScreenTurnedOff() { - if (mKeyguardViewController != null - && mRoot != null && mRoot.getVisibility() == View.VISIBLE) { + if (mKeyguardViewController != null && mContainer.getVisibility() == View.VISIBLE) { mKeyguardViewController.onPause(); } } public boolean isShowing() { - return (mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE)) + return (mShowingSoon || mContainer.getVisibility() == View.VISIBLE) && mExpansion == EXPANSION_VISIBLE && !isAnimatingAway(); } @@ -401,7 +391,7 @@ public class KeyguardBouncer { } public void prepare() { - boolean wasInitialized = mRoot != null; + boolean wasInitialized = mInitialized; ensureView(); if (wasInitialized) { showPrimarySecurityScreen(); @@ -461,7 +451,7 @@ public class KeyguardBouncer { // in this case we need to force the removal, otherwise we'll // end up in an unpredictable state. boolean forceRemoval = mHandler.hasCallbacks(mRemoveViewRunnable); - if (mRoot == null || forceRemoval) { + if (!mInitialized || forceRemoval) { inflateView(); } } @@ -469,28 +459,24 @@ public class KeyguardBouncer { protected void inflateView() { removeView(); mHandler.removeCallbacks(mRemoveViewRunnable); - KeyguardBouncerComponent component = mKeyguardBouncerComponentFactory.create(); - mRootViewController = component.getKeyguardRootViewController(); - mRootViewController.init(); - mRoot = mRootViewController.getView(); // TODO(b/166448040): Don't access root view here. + + KeyguardBouncerComponent component = mKeyguardBouncerComponentFactory.create(mContainer); mKeyguardViewController = component.getKeyguardHostViewController(); mKeyguardViewController.init(); - mContainer.addView(mRoot, mContainer.getChildCount()); mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); setVisibility(View.INVISIBLE); - final WindowInsets rootInsets = mRoot.getRootWindowInsets(); + final WindowInsets rootInsets = mContainer.getRootWindowInsets(); if (rootInsets != null) { - mRoot.dispatchApplyWindowInsets(rootInsets); + mContainer.dispatchApplyWindowInsets(rootInsets); } + mInitialized = true; } protected void removeView() { - if (mRoot != null && mRoot.getParent() == mContainer) { - mContainer.removeView(mRoot); - mRoot = null; - } + mContainer.removeAllViews(); + mInitialized = false; } /** @@ -577,7 +563,7 @@ public class KeyguardBouncer { private void dispatchVisibilityChanged() { for (BouncerExpansionCallback callback : mExpansionCallbacks) { - callback.onVisibilityChanged(mRoot.getVisibility() == View.VISIBLE); + callback.onVisibilityChanged(mContainer.getVisibility() == View.VISIBLE); } } @@ -601,6 +587,7 @@ public class KeyguardBouncer { pw.println(" mShowingSoon: " + mShowingSoon); pw.println(" mBouncerPromptReason: " + mBouncerPromptReason); pw.println(" mIsAnimatingAway: " + mIsAnimatingAway); + pw.println(" mInitialized: " + mInitialized); } /** Update keyguard position based on a tapped X coordinate. */ @@ -675,7 +662,10 @@ public class KeyguardBouncer { mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory; } - public KeyguardBouncer create(@RootView ViewGroup container, + /** + * Construct a KeyguardBouncer that will exist in the given container. + */ + public KeyguardBouncer create(ViewGroup container, BouncerExpansionCallback expansionCallback) { return new KeyguardBouncer(mContext, mCallback, container, mDismissCallbackRegistry, mFalsingCollector, expansionCallback, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 7ca8652e1b3c..732e5f0343a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -57,8 +57,7 @@ public class KeyguardClockPositionAlgorithm { private int mUserSwitchPreferredY; /** - * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher - * avatar. + * Minimum top margin to avoid overlap with status bar or multi-user switcher avatar. */ private int mMinTopMargin; @@ -203,7 +202,7 @@ public class KeyguardClockPositionAlgorithm { if (mBypassEnabled) { return mUnlockedStackScrollerPadding; } else if (mIsSplitShade) { - return getClockY(1.0f, mDarkAmount); + return getClockY(1.0f, mDarkAmount) + mUserSwitchHeight; } else { return getClockY(1.0f, mDarkAmount) + mKeyguardStatusHeight; } @@ -213,7 +212,7 @@ public class KeyguardClockPositionAlgorithm { if (mBypassEnabled) { return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount); } else if (mIsSplitShade) { - return Math.max(0, clockYPosition - mSplitShadeTopNotificationsMargin); + return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight; } else { return clockYPosition + mKeyguardStatusHeight; } @@ -223,7 +222,7 @@ public class KeyguardClockPositionAlgorithm { if (mBypassEnabled) { return mUnlockedStackScrollerPadding; } else if (mIsSplitShade) { - return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin); + return mSplitShadeTargetTopMargin + mUserSwitchHeight; } else { return mMinTopMargin + mKeyguardStatusHeight; } @@ -231,7 +230,7 @@ public class KeyguardClockPositionAlgorithm { private int getExpandedPreferredClockY() { if (mIsSplitShade) { - return Math.max(mSplitShadeTargetTopMargin, mMinTopMargin); + return mSplitShadeTargetTopMargin; } else { return mMinTopMargin; } 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 434671c02035..03b1627b03c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.view.View.GONE; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; @@ -197,6 +198,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.Utils; import com.android.systemui.util.settings.SecureSettings; @@ -732,7 +734,9 @@ public class NotificationPanelViewController extends PanelViewController { NotificationEntryManager notificationEntryManager, CommunalStateController communalStateController, KeyguardStateController keyguardStateController, - StatusBarStateController statusBarStateController, DozeLog dozeLog, + StatusBarStateController statusBarStateController, + StatusBarWindowStateController statusBarWindowStateController, + DozeLog dozeLog, DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper, LatencyTracker latencyTracker, PowerManager powerManager, AccessibilityManager accessibilityManager, @DisplayId int displayId, @@ -862,6 +866,7 @@ public class NotificationPanelViewController extends PanelViewController { mQs.animateHeaderSlidingOut(); } }); + statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged); mThemeResId = mView.getContext().getThemeResId(); mKeyguardBypassController = bypassController; mUpdateMonitor = keyguardUpdateMonitor; @@ -1440,8 +1445,11 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange); } updateKeyguardStatusViewAlignment(true /* animate */); - int userIconHeight = mKeyguardQsUserSwitchController != null + int userSwitcherHeight = mKeyguardQsUserSwitchController != null ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0; + if (mKeyguardUserSwitcherController != null) { + userSwitcherHeight = mKeyguardUserSwitcherController.getHeight(); + } float expandedFraction = mScreenOffAnimationController.shouldExpandNotifications() ? 1.0f : getExpandedFraction(); @@ -1461,7 +1469,7 @@ public class NotificationPanelViewController extends PanelViewController { mStatusBarHeaderHeightKeyguard, expandedFraction, mKeyguardStatusViewController.getLockscreenHeight(), - userIconHeight, + userSwitcherHeight, userSwitcherPreferredY, darkamount, mOverStretchAmount, bypassEnabled, getUnlockedStackScrollerPadding(), @@ -5006,4 +5014,14 @@ public class NotificationPanelViewController extends PanelViewController { public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() { return mStatusBarViewTouchEventHandler; } + + private void onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState int state) { + if (state != WINDOW_STATE_SHOWING + && mStatusBarStateController.getState() == StatusBarState.SHADE) { + collapsePanel( + false /* animate */, + false /* delayed */, + 1.0f /* speedUpFactor */); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java index 72f169564c10..fb0e306b2e8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java @@ -50,7 +50,7 @@ import android.view.WindowInsetsController; import android.widget.FrameLayout; import com.android.internal.view.FloatingActionMode; -import com.android.internal.widget.FloatingToolbar; +import com.android.internal.widget.floatingtoolbar.FloatingToolbar; import com.android.systemui.R; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 81d4bbf1e3c8..4e2eb6a19d53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -16,10 +16,7 @@ package com.android.systemui.statusbar.phone; -import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; - import android.app.StatusBarManager; -import android.graphics.RectF; import android.hardware.display.AmbientDisplayConfiguration; import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; @@ -39,24 +36,15 @@ import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; -import com.android.systemui.doze.DozeLog; -import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.tuner.TunerService; import java.io.FileDescriptor; @@ -69,27 +57,16 @@ import javax.inject.Inject; */ public class NotificationShadeWindowViewController { private static final String TAG = "NotifShadeWindowVC"; - private final NotificationWakeUpCoordinator mCoordinator; - private final PulseExpansionHandler mPulseExpansionHandler; - private final DynamicPrivacyController mDynamicPrivacyController; - private final KeyguardBypassController mBypassController; - private final PluginManager mPluginManager; private final FalsingCollector mFalsingCollector; private final TunerService mTunerService; - private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; - private final NotificationEntryManager mNotificationEntryManager; - private final KeyguardStateController mKeyguardStateController; private final SysuiStatusBarStateController mStatusBarStateController; - private final DozeLog mDozeLog; - private final DozeParameters mDozeParameters; - private final CommandQueue mCommandQueue; private final NotificationShadeWindowView mView; - private final ShadeController mShadeController; private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final LockIconViewController mLockIconViewController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final StatusBarWindowStateController mStatusBarWindowStateController; private GestureDetector mGestureDetector; private View mBrightnessMirror; @@ -97,7 +74,7 @@ public class NotificationShadeWindowViewController { private boolean mTouchCancelled; private boolean mExpandAnimationRunning; private NotificationStackScrollLayout mStackScrollLayout; - private PhoneStatusBarView mStatusBarView; + private PhoneStatusBarViewController mStatusBarViewController; private StatusBar mService; private NotificationShadeWindowController mNotificationShadeWindowController; private DragDownHelper mDragDownHelper; @@ -107,64 +84,36 @@ public class NotificationShadeWindowViewController { private final DockManager mDockManager; private final NotificationPanelViewController mNotificationPanelViewController; private final PanelExpansionStateManager mPanelExpansionStateManager; - private final StatusBarWindowController mStatusBarWindowController; - // Used for determining view / touch intersection - private int[] mTempLocation = new int[2]; - private RectF mTempRect = new RectF(); private boolean mIsTrackingBarGesture = false; @Inject public NotificationShadeWindowViewController( - NotificationWakeUpCoordinator coordinator, - PulseExpansionHandler pulseExpansionHandler, - DynamicPrivacyController dynamicPrivacyController, - KeyguardBypassController bypassController, LockscreenShadeTransitionController transitionController, FalsingCollector falsingCollector, - PluginManager pluginManager, TunerService tunerService, - NotificationLockscreenUserManager notificationLockscreenUserManager, - NotificationEntryManager notificationEntryManager, - KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, - DozeLog dozeLog, - DozeParameters dozeParameters, - CommandQueue commandQueue, - ShadeController shadeController, DockManager dockManager, NotificationShadeDepthController depthController, NotificationShadeWindowView notificationShadeWindowView, NotificationPanelViewController notificationPanelViewController, PanelExpansionStateManager panelExpansionStateManager, - StatusBarWindowController statusBarWindowController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + StatusBarWindowStateController statusBarWindowStateController, LockIconViewController lockIconViewController) { - mCoordinator = coordinator; - mPulseExpansionHandler = pulseExpansionHandler; - mDynamicPrivacyController = dynamicPrivacyController; - mBypassController = bypassController; mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; - mPluginManager = pluginManager; mTunerService = tunerService; - mNotificationLockscreenUserManager = notificationLockscreenUserManager; - mNotificationEntryManager = notificationEntryManager; - mKeyguardStateController = keyguardStateController; mStatusBarStateController = statusBarStateController; - mDozeLog = dozeLog; - mDozeParameters = dozeParameters; - mCommandQueue = commandQueue; mView = notificationShadeWindowView; - mShadeController = shadeController; mDockManager = dockManager; mNotificationPanelViewController = notificationPanelViewController; mPanelExpansionStateManager = panelExpansionStateManager; mDepthController = depthController; - mStatusBarWindowController = statusBarWindowController; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mStatusBarWindowStateController = statusBarWindowStateController; mLockIconViewController = lockIconViewController; // This view is not part of the newly inflated expanded status bar. @@ -225,7 +174,7 @@ public class NotificationShadeWindowViewController { mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { @Override public Boolean handleDispatchTouchEvent(MotionEvent ev) { - if (mStatusBarView == null) { + if (mStatusBarViewController == null) { // Fix for b/192490822 Log.w(TAG, "Ignoring touch while statusBarView not yet set."); return false; } @@ -247,11 +196,11 @@ public class NotificationShadeWindowViewController { } if (isDown) { - setTouchActive(true); + mTouchActive = true; mTouchCancelled = false; } else if (ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { - setTouchActive(false); + mTouchActive = false; } if (mTouchCancelled || mExpandAnimationRunning) { return false; @@ -293,27 +242,27 @@ public class NotificationShadeWindowViewController { expandingBelowNotch = true; } if (expandingBelowNotch) { - return mStatusBarView.dispatchTouchEvent(ev); + return mStatusBarViewController.sendTouchToView(ev); } if (!mIsTrackingBarGesture && isDown && mNotificationPanelViewController.isFullyCollapsed()) { float x = ev.getRawX(); float y = ev.getRawY(); - if (isIntersecting(mStatusBarView, x, y)) { - if (mService.isSameStatusBarState(WINDOW_STATE_SHOWING)) { + if (mStatusBarViewController.touchIsWithinView(x, y)) { + if (mStatusBarWindowStateController.windowIsShowing()) { mIsTrackingBarGesture = true; - return mStatusBarView.dispatchTouchEvent(ev); + return mStatusBarViewController.sendTouchToView(ev); } else { // it's hidden or hiding, don't send to notification shade. return true; } } } else if (mIsTrackingBarGesture) { - final boolean sendToNotification = mStatusBarView.dispatchTouchEvent(ev); + final boolean sendToStatusBar = mStatusBarViewController.sendTouchToView(ev); if (isUp || isCancel) { mIsTrackingBarGesture = false; } - return sendToNotification; + return sendToStatusBar; } return null; @@ -458,10 +407,6 @@ public class NotificationShadeWindowViewController { return mView; } - public void setTouchActive(boolean touchActive) { - mTouchActive = touchActive; - } - public void cancelCurrentTouch() { if (mTouchActive) { final long now = SystemClock.uptimeMillis(); @@ -496,8 +441,8 @@ public class NotificationShadeWindowViewController { } } - public void setStatusBarView(PhoneStatusBarView statusBarView) { - mStatusBarView = statusBarView; + public void setStatusBarViewController(PhoneStatusBarViewController statusBarViewController) { + mStatusBarViewController = statusBarViewController; } public void setService(StatusBar statusBar, NotificationShadeWindowController controller) { @@ -509,11 +454,4 @@ public class NotificationShadeWindowViewController { void setDragDownHelper(DragDownHelper dragDownHelper) { mDragDownHelper = dragDownHelper; } - - private boolean isIntersecting(View view, float x, float y) { - mTempLocation = view.getLocationOnScreen(); - mTempRect.set(mTempLocation[0], mTempLocation[1], mTempLocation[0] + view.getWidth(), - mTempLocation[1] + view.getHeight()); - return mTempRect.contains(x, y); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index b9386bd01e03..1cb19ab727aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone import android.content.res.Configuration import android.graphics.Point +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver @@ -92,6 +93,30 @@ class PhoneStatusBarViewController private constructor( mView.importantForAccessibility = mode } + /** + * Sends a touch event to the status bar view. + * + * This is required in certain cases because the status bar view is in a separate window from + * the rest of SystemUI, and other windows may decide that their touch should instead be treated + * as a status bar window touch. + */ + fun sendTouchToView(ev: MotionEvent): Boolean { + return mView.dispatchTouchEvent(ev) + } + + /** + * Returns true if the given (x, y) point (in screen coordinates) is within the status bar + * view's range and false otherwise. + */ + fun touchIsWithinView(x: Float, y: Float): Boolean { + val left = mView.locationOnScreen[0] + val top = mView.locationOnScreen[1] + return left <= x && + x <= left + mView.width && + top <= y && + y <= top + mView.height + } + class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider { override fun getViewCenter(view: View, outPoint: Point) = when (view.id) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java index 2d41e5e9be2c..24bb7f25f2eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java @@ -55,6 +55,9 @@ public interface ShadeController { */ boolean closeShadeIfOpen(); + /** Returns whether the shade is currently open or opening. */ + boolean isShadeOpen(); + /** * Add a runnable for NotificationPanelView to post when the panel is expanded. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index b4fed2ba8624..f8b0535b7ec7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -148,6 +148,13 @@ public class ShadeControllerImpl implements ShadeController { } @Override + public boolean isShadeOpen() { + NotificationPanelViewController controller = + getNotificationPanelViewController(); + return controller.isExpanding() || controller.isFullyExpanded(); + } + + @Override public void postOnShadeExpanded(Runnable executable) { getNotificationPanelViewController().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 2ba70df8a1da..1cc5e12a4102 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -70,6 +70,7 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.PointF; +import android.hardware.devicestate.DeviceStateManager; import android.metrics.LogMaker; import android.net.Uri; import android.os.Bundle; @@ -116,6 +117,7 @@ import androidx.lifecycle.LifecycleRegistry; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -232,6 +234,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -337,15 +340,9 @@ public class StatusBar extends CoreStartable implements private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private StatusBarCommandQueueCallbacks mCommandQueueCallbacks; - void setWindowState(int state) { - mStatusBarWindowState = state; - mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN; - mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden); - if (mStatusBarView != null) { - // Should #updateHideIconsForBouncer always be called, regardless of whether we have a - // status bar view? If so, we can make #updateHideIconsForBouncer private. - mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false); - } + void onStatusBarWindowStateChanged(@WindowVisibleState int state) { + updateBubblesVisibility(); + mStatusBarWindowState = state; } void acquireGestureWakeLock(long time) { @@ -464,7 +461,7 @@ public class StatusBar extends CoreStartable implements private PhoneStatusBarViewController mPhoneStatusBarViewController; private PhoneStatusBarTransitions mStatusBarTransitions; private AuthRippleController mAuthRippleController; - private int mStatusBarWindowState = WINDOW_STATE_SHOWING; + @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING; protected NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarWindowController mStatusBarWindowController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -677,6 +674,8 @@ public class StatusBar extends CoreStartable implements private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener = (extractor, which) -> updateTheme(); + private final InteractionJankMonitor mJankMonitor; + /** * Public constructor for StatusBar. @@ -692,6 +691,7 @@ public class StatusBar extends CoreStartable implements LightBarController lightBarController, AutoHideController autoHideController, StatusBarWindowController statusBarWindowController, + StatusBarWindowStateController statusBarWindowStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, StatusBarSignalPolicy statusBarSignalPolicy, PulseExpansionHandler pulseExpansionHandler, @@ -779,7 +779,9 @@ public class StatusBar extends CoreStartable implements WallpaperManager wallpaperManager, Optional<StartingSurface> startingSurfaceOptional, ActivityLaunchAnimator activityLaunchAnimator, - NotifPipelineFlags notifPipelineFlags) { + NotifPipelineFlags notifPipelineFlags, + InteractionJankMonitor jankMonitor, + DeviceStateManager deviceStateManager) { super(context); mNotificationsController = notificationsController; mFragmentService = fragmentService; @@ -867,11 +869,13 @@ public class StatusBar extends CoreStartable implements mMainExecutor = delayableExecutor; mMessageRouter = messageRouter; mWallpaperManager = wallpaperManager; + mJankMonitor = jankMonitor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; mNotifPipelineFlags = notifPipelineFlags; lockscreenShadeTransitionController.setStatusbar(this); + statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged); mScreenOffAnimationController = screenOffAnimationController; @@ -900,6 +904,9 @@ public class StatusBar extends CoreStartable implements data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel)); mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT, id -> onLaunchTransitionTimeout()); + + deviceStateManager.registerCallback(mMainExecutor, + new FoldStateListener(mContext, this::onFoldedStateChanged)); } @Override @@ -1098,6 +1105,27 @@ public class StatusBar extends CoreStartable implements requestTopUi, componentTag)))); } + private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) { + // Folded state changes are followed by a screen off event. + // By default turning off the screen also closes the shade. + // We want to make sure that the shade status is kept after + // folding/unfolding. + boolean isShadeOpen = mShadeController.isShadeOpen(); + boolean leaveOpen = isShadeOpen && !willGoToSleep; + if (DEBUG) { + Log.d(TAG, String.format( + "#onFoldedStateChanged(): " + + "isFolded=%s, " + + "willGoToSleep=%s, " + + "isShadeOpen=%s, " + + "leaveOpen=%s", + isFolded, willGoToSleep, isShadeOpen, leaveOpen)); + } + if (leaveOpen) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + } + } + // ================================================================================ // Constructing the view // ================================================================================ @@ -1133,7 +1161,8 @@ public class StatusBar extends CoreStartable implements mStatusBarView = statusBarView; mPhoneStatusBarViewController = statusBarViewController; mStatusBarTransitions = statusBarTransitions; - mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView); + mNotificationShadeWindowViewController + .setStatusBarViewController(mPhoneStatusBarViewController); // Ensure we re-propagate panel expansion values to the panel controller and // any listeners it may have, such as PanelBar. This will also ensure we // re-display the notification panel if necessary (for example, if @@ -1387,7 +1416,8 @@ public class StatusBar extends CoreStartable implements mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider( mNotificationShadeWindowViewController, mStackScrollerController.getNotificationListContainer(), - mHeadsUpManager + mHeadsUpManager, + mJankMonitor ); // TODO: inject this. @@ -2105,10 +2135,6 @@ public class StatusBar extends CoreStartable implements } } - boolean isSameStatusBarState(int state) { - return mStatusBarWindowState == state; - } - public GestureRecorder getGestureRecorder() { return mGestureRec; } @@ -3586,7 +3612,7 @@ public class StatusBar extends CoreStartable implements final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override - public void onScreenTurningOn() { + public void onScreenTurningOn(Runnable onDrawn) { mFalsingCollector.onScreenTurningOn(); mNotificationPanelViewController.onScreenTurningOn(); } 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 b391de3c5ab1..51f2e6728b80 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; -import static android.app.StatusBarManager.windowStateToString; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.containsType; @@ -58,7 +56,6 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.qs.QSPanelController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; -import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -515,30 +512,6 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { } @Override - public void setWindowState( - int displayId, @StatusBarManager.WindowType int window, - @StatusBarManager.WindowVisibleState int state) { - if (displayId != mDisplayId) { - return; - } - boolean showing = state == WINDOW_STATE_SHOWING; - if (mNotificationShadeWindowView != null - && window == StatusBarManager.WINDOW_STATUS_BAR - && !mStatusBar.isSameStatusBarState(state)) { - mStatusBar.setWindowState(state); - if (StatusBar.DEBUG_WINDOW_STATE) { - Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state)); - } - if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) { - mNotificationPanelViewController.collapsePanel( - false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */); - } - } - - mStatusBar.updateBubblesVisibility(); - } - - @Override public void showAssistDisclosure() { mAssistManager.showDisclosure(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt index d2181d0480d2..17516e07400b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt @@ -1,10 +1,12 @@ package com.android.systemui.statusbar.phone +import android.app.StatusBarManager import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.concurrency.DelayableExecutor import java.io.FileDescriptor import java.io.PrintWriter @@ -25,6 +27,7 @@ import javax.inject.Inject class StatusBarHideIconsForBouncerManager @Inject constructor( private val commandQueue: CommandQueue, @Main private val mainExecutor: DelayableExecutor, + statusBarWindowStateController: StatusBarWindowStateController, dumpManager: DumpManager ) : Dumpable { // State variables set by external classes. @@ -42,6 +45,9 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( init { dumpManager.registerDumpable(this) + statusBarWindowStateController.addListener { + state -> setStatusBarStateAndTriggerUpdate(state) + } } /** Returns true if the status bar icons should be hidden in the bouncer. */ @@ -49,8 +55,9 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( return hideIconsForBouncer || wereIconsJustHidden } - fun setStatusBarWindowHidden(statusBarWindowHidden: Boolean) { - this.statusBarWindowHidden = statusBarWindowHidden + private fun setStatusBarStateAndTriggerUpdate(@StatusBarManager.WindowVisibleState state: Int) { + statusBarWindowHidden = state == StatusBarManager.WINDOW_STATE_HIDDEN + updateHideIconsForBouncer(animate = false) } fun setDisplayId(displayId: Int) { @@ -87,7 +94,7 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( * Updates whether the status bar icons should be hidden in the bouncer. May trigger * [commandQueue.recomputeDisableFlags] if the icon visibility status changes. */ - fun updateHideIconsForBouncer(animate: Boolean) { + private fun updateHideIconsForBouncer(animate: Boolean) { val hideBecauseApp = topAppHidesStatusBar && isOccluded && 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 875b7e59d15a..316e68227e0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -51,7 +51,6 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.DejankUtils; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; -import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -87,7 +86,7 @@ import dagger.Lazy; public class StatusBarKeyguardViewManager implements RemoteInputController.Callback, StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener, PanelExpansionListener, NavigationModeController.ModeChangedListener, - KeyguardViewController, WakefulnessLifecycle.Observer { + KeyguardViewController { // When hiding the Keyguard with timing supplied from WindowManager, better be early than late. private static final long HIDE_TIMING_CORRECTION_MS = - 16 * 3; @@ -392,17 +391,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } else { mStatusBar.showKeyguard(); if (hideBouncerWhenShowing) { - hideBouncer(shouldDestroyViewOnReset() /* destroyView */); + hideBouncer(false /* destroyView */); mBouncer.prepare(); } } updateStates(); } - protected boolean shouldDestroyViewOnReset() { - return false; - } - /** * If applicable, shows the alternate authentication bouncer. Else, shows the input * (pin/password/pattern) bouncer. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index f93a8dcad223..977fe9c2d201 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -20,10 +20,12 @@ import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import android.app.WallpaperManager; import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; import android.os.Handler; import android.os.PowerManager; import android.util.DisplayMetrics; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; @@ -105,6 +107,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.MessageRouter; @@ -140,6 +143,7 @@ public interface StatusBarPhoneModule { LightBarController lightBarController, AutoHideController autoHideController, StatusBarWindowController statusBarWindowController, + StatusBarWindowStateController statusBarWindowStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, StatusBarSignalPolicy statusBarSignalPolicy, PulseExpansionHandler pulseExpansionHandler, @@ -227,7 +231,9 @@ public interface StatusBarPhoneModule { WallpaperManager wallpaperManager, Optional<StartingSurface> startingSurfaceOptional, ActivityLaunchAnimator activityLaunchAnimator, - NotifPipelineFlags notifPipelineFlags) { + NotifPipelineFlags notifPipelineFlags, + InteractionJankMonitor jankMonitor, + DeviceStateManager deviceStateManager) { return new StatusBar( context, notificationsController, @@ -235,6 +241,7 @@ public interface StatusBarPhoneModule { lightBarController, autoHideController, statusBarWindowController, + statusBarWindowStateController, keyguardUpdateMonitor, statusBarSignalPolicy, pulseExpansionHandler, @@ -321,7 +328,9 @@ public interface StatusBarPhoneModule { wallpaperManager, startingSurfaceOptional, activityLaunchAnimator, - notifPipelineFlags + notifPipelineFlags, + jankMonitor, + deviceStateManager ); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 2dfcc98b5037..c1f63b80dbf8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -171,6 +171,8 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum if (mShowing == showing && mOccluded == occluded) return; mShowing = showing; mOccluded = occluded; + Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", + "Keyguard showing: " + showing + " occluded: " + occluded); notifyKeyguardChanged(); // Update the dismiss amount to the full 0f/1f if we explicitly show or hide the keyguard. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index ffa7963d9565..04a6a114a07d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -248,6 +248,10 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS return mUserSwitcherController.isSimpleUserSwitcher(); } + public int getHeight() { + return mListView.getHeight(); + } + /** * @param animate if the transition should be animated * @return true if the switcher state changed diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java index cd8894cae684..850a4b499562 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java @@ -97,9 +97,12 @@ public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout { } else { // Update clickable state immediately so that the menu feels more responsive userItemViews[i].setClickable(open); - // Before running the animation, ensure visibility is set correctly - userItemViews[i].updateVisibilities(animate || open /* showItem */, - true /* showTextName */, false /* animate */); + // when opening we need to make views visible beforehand so they can be animated + if (open) { + userItemViews[i].updateVisibilities(true /* showItem */, + true /* showTextName */, false /* animate */); + } + } } @@ -117,6 +120,13 @@ public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout { setClipChildren(true); setClipToPadding(true); mAnimating = false; + if (!open) { + // after closing we hide children so that height of this view is correct + for (int i = 1; i < userItemViews.length; i++) { + userItemViews[i].updateVisibilities(false /* showItem */, + true /* showTextName */, false /* animate */); + } + } }); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java index f258fb19ff7d..1158324567ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java @@ -23,6 +23,7 @@ public interface RotationLockController extends Listenable, int getRotationLockOrientation(); boolean isRotationLockAffordanceVisible(); boolean isRotationLocked(); + boolean isCameraRotationEnabled(); void setRotationLocked(boolean locked); void setRotationLockedAtAngle(boolean locked, int rotation); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java index 67f5364e3d3f..3143a471649c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java @@ -18,6 +18,9 @@ package com.android.systemui.statusbar.policy; import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS; +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -34,6 +37,7 @@ import javax.inject.Named; /** Platform implementation of the rotation lock controller. **/ @SysUISingleton public final class RotationLockControllerImpl implements RotationLockController { + private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks = new CopyOnWriteArrayList<>(); @@ -86,11 +90,15 @@ public final class RotationLockControllerImpl implements RotationLockController return mRotationPolicy.isRotationLocked(); } + public boolean isCameraRotationEnabled() { + return mRotationPolicy.isCameraRotationEnabled(); + } + public void setRotationLocked(boolean locked) { mRotationPolicy.setRotationLock(locked); } - public void setRotationLockedAtAngle(boolean locked, int rotation){ + public void setRotationLockedAtAngle(boolean locked, int rotation) { mRotationPolicy.setRotationLockAtAngle(locked, rotation); } @@ -121,4 +129,11 @@ public final class RotationLockControllerImpl implements RotationLockController callback.onRotationLockStateChanged(mRotationPolicy.isRotationLocked(), mRotationPolicy.isRotationLockToggleVisible()); } + + public static boolean hasSufficientPermission(Context context) { + final PackageManager packageManager = context.getPackageManager(); + final String rotationPackage = packageManager.getRotationResolverPackageName(); + return rotationPackage != null && packageManager.checkPermission( + Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt new file mode 100644 index 000000000000..3a1491400f38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt @@ -0,0 +1,88 @@ +/* + * 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.statusbar.window + +import android.app.StatusBarManager +import android.app.StatusBarManager.WindowVisibleState +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR +import android.app.StatusBarManager.windowStateToString +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.phone.StatusBar +import javax.inject.Inject + +/** + * A centralized class maintaining the state of the status bar window. + * + * Classes that want to get updates about the status bar window state should subscribe to this class + * via [addListener] and should NOT add their own callback on [CommandQueue]. + */ +@SysUISingleton +class StatusBarWindowStateController @Inject constructor( + @DisplayId private val thisDisplayId: Int, + commandQueue: CommandQueue +) { + private val commandQueueCallback = object : CommandQueue.Callbacks { + override fun setWindowState( + displayId: Int, + @StatusBarManager.WindowType window: Int, + @WindowVisibleState state: Int + ) { + this@StatusBarWindowStateController.setWindowState(displayId, window, state) + } + } + private val listeners: MutableSet<StatusBarWindowStateListener> = HashSet() + + @WindowVisibleState private var windowState: Int = WINDOW_STATE_SHOWING + + init { + commandQueue.addCallback(commandQueueCallback) + } + + /** Adds a listener. */ + fun addListener(listener: StatusBarWindowStateListener) { + listeners.add(listener) + } + + /** Returns true if the window is currently showing. */ + fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING + + private fun setWindowState( + displayId: Int, + @StatusBarManager.WindowType window: Int, + @WindowVisibleState state: Int + ) { + if (displayId != thisDisplayId) { + return + } + if (window != WINDOW_STATUS_BAR) { + return + } + if (windowState == state) { + return + } + + windowState = state + if (StatusBar.DEBUG_WINDOW_STATE) { + Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state)) + } + listeners.forEach { it.onStatusBarWindowStateChanged(state) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateListener.kt new file mode 100644 index 000000000000..f3bab04d97a0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateListener.kt @@ -0,0 +1,24 @@ +/* + * 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.statusbar.window + +import android.app.StatusBarManager + +/** Listener interface for changes in the status bar window state. */ +fun interface StatusBarWindowStateListener { + fun onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState state: Int) +} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index fb9df01a0251..52c416bad803 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -19,7 +19,6 @@ package com.android.systemui.unfold import android.os.PowerManager import android.provider.Settings import com.android.systemui.keyguard.KeyguardViewMediator -import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.phone.ScreenOffAnimation @@ -36,12 +35,10 @@ import javax.inject.Inject */ @SysUIUnfoldScope class FoldAodAnimationController @Inject constructor( - private val screenLifecycle: ScreenLifecycle, private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val wakefulnessLifecycle: WakefulnessLifecycle, private val globalSettings: GlobalSettings -) : ScreenLifecycle.Observer, - CallbackController<FoldAodAnimationStatus>, +) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer { @@ -58,7 +55,6 @@ class FoldAodAnimationController @Inject constructor( override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) { this.statusBar = statusBar - screenLifecycle.addObserver(this) wakefulnessLifecycle.addObserver(this) } @@ -123,7 +119,7 @@ class FoldAodAnimationController @Inject constructor( } } - override fun onScreenTurnedOn() { + fun onScreenTurnedOn() { if (shouldPlayAnimation) { statusBar.notificationPanelViewController.startFoldToAodAnimation { // End action diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index cd3e2d335f0b..75dfd48ad9f3 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -58,7 +58,8 @@ class UnfoldTransitionModule { deviceStateManager, sensorManager, handler, - executor + executor, + tracingTagPrefix = "systemui" ) ) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt index a60033cf40fe..6cd384f17803 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.keyguard +package com.android.systemui.util.concurrency import android.os.Trace import java.util.concurrent.atomic.AtomicInteger @@ -23,9 +23,9 @@ import java.util.concurrent.atomic.AtomicReference /** * Allows to wait for multiple callbacks and notify when the last one is executed */ -class PendingDrawnTasksContainer { +class PendingTasksContainer { - private var pendingDrawnTasksCount: AtomicInteger = AtomicInteger(0) + private var pendingTasksCount: AtomicInteger = AtomicInteger(0) private var completionCallback: AtomicReference<Runnable> = AtomicReference() /** @@ -33,19 +33,19 @@ class PendingDrawnTasksContainer { * @return a runnable that should be invoked when the task is finished */ fun registerTask(name: String): Runnable { - pendingDrawnTasksCount.incrementAndGet() + pendingTasksCount.incrementAndGet() if (ENABLE_TRACE) { - Trace.beginAsyncSection("PendingDrawnTasksContainer#$name", 0) + Trace.beginAsyncSection("PendingTasksContainer#$name", 0) } return Runnable { - if (pendingDrawnTasksCount.decrementAndGet() == 0) { + if (pendingTasksCount.decrementAndGet() == 0) { val onComplete = completionCallback.getAndSet(null) onComplete?.run() if (ENABLE_TRACE) { - Trace.endAsyncSection("PendingDrawnTasksContainer#$name", 0) + Trace.endAsyncSection("PendingTasksContainer#$name", 0) } } } @@ -57,7 +57,7 @@ class PendingDrawnTasksContainer { fun reset() { // Create new objects in case if there are pending callbacks from the previous invocations completionCallback = AtomicReference() - pendingDrawnTasksCount = AtomicInteger(0) + pendingTasksCount = AtomicInteger(0) } /** @@ -67,7 +67,7 @@ class PendingDrawnTasksContainer { fun onTasksComplete(onComplete: Runnable) { completionCallback.set(onComplete) - if (pendingDrawnTasksCount.get() == 0) { + if (pendingTasksCount.get() == 0) { val currentOnComplete = completionCallback.getAndSet(null) currentOnComplete?.run() } @@ -76,7 +76,7 @@ class PendingDrawnTasksContainer { /** * Returns current pending tasks count */ - fun getPendingCount(): Int = pendingDrawnTasksCount.get() + fun getPendingCount(): Int = pendingTasksCount.get() } private const val ENABLE_TRACE = false diff --git a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt index 2a0cc7ddacf5..b64d7bec184f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt @@ -17,8 +17,10 @@ package com.android.systemui.util.wrapper import android.content.Context +import android.provider.Settings.Secure.CAMERA_AUTOROTATE import com.android.internal.view.RotationPolicy import com.android.internal.view.RotationPolicy.RotationPolicyListener +import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject /** @@ -30,12 +32,16 @@ interface RotationPolicyWrapper { fun getRotationLockOrientation(): Int fun isRotationLockToggleVisible(): Boolean fun isRotationLocked(): Boolean + fun isCameraRotationEnabled(): Boolean fun registerRotationPolicyListener(listener: RotationPolicyListener, userHandle: Int) fun unregisterRotationPolicyListener(listener: RotationPolicyListener) } -class RotationPolicyWrapperImpl @Inject constructor(private val context: Context) : - RotationPolicyWrapper { +class RotationPolicyWrapperImpl @Inject constructor( + private val context: Context, + private val secureSettings: SecureSettings +) : + RotationPolicyWrapper { override fun setRotationLock(enabled: Boolean) { RotationPolicy.setRotationLock(context, enabled) @@ -54,6 +60,9 @@ class RotationPolicyWrapperImpl @Inject constructor(private val context: Context override fun isRotationLocked(): Boolean = RotationPolicy.isRotationLocked(context) + override fun isCameraRotationEnabled(): Boolean = + secureSettings.getInt(CAMERA_AUTOROTATE, 0) == 1 + override fun registerRotationPolicyListener( listener: RotationPolicyListener, userHandle: Int diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt new file mode 100644 index 000000000000..96e6bd15a3e2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt @@ -0,0 +1,136 @@ +/* + * 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.keyguard.mediator + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest + +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.ScreenLifecycle +import com.android.systemui.unfold.FoldAodAnimationController +import com.android.systemui.unfold.SysUIUnfoldComponent +import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation +import com.android.systemui.util.concurrency.FakeExecution +import com.android.systemui.util.mockito.capture + +import java.util.Optional + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ScreenOnCoordinatorTest : SysuiTestCase() { + + @Mock + private lateinit var runnable: Runnable + @Mock + private lateinit var unfoldComponent: SysUIUnfoldComponent + @Mock + private lateinit var foldAodAnimationController: FoldAodAnimationController + @Mock + private lateinit var unfoldAnimation: UnfoldLightRevealOverlayAnimation + @Mock + private lateinit var screenLifecycle: ScreenLifecycle + @Captor + private lateinit var readyCaptor: ArgumentCaptor<Runnable> + + private lateinit var screenOnCoordinator: ScreenOnCoordinator + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(unfoldComponent.getUnfoldLightRevealOverlayAnimation()) + .thenReturn(unfoldAnimation) + `when`(unfoldComponent.getFoldAodAnimationController()) + .thenReturn(foldAodAnimationController) + + screenOnCoordinator = ScreenOnCoordinator( + screenLifecycle, + Optional.of(unfoldComponent), + FakeExecution() + ) + + // Make sure screen events are registered to observe + verify(screenLifecycle).addObserver(screenOnCoordinator) + } + + @Test + fun testUnfoldTransitionEnabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() { + screenOnCoordinator.onScreenTurningOn(runnable) + + onUnfoldOverlayReady() + onFoldAodReady() + + // Should be called when both unfold overlay and keyguard drawn ready + verify(runnable).run() + } + + @Test + fun testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() { + // Recreate with empty unfoldComponent + screenOnCoordinator = ScreenOnCoordinator( + screenLifecycle, + Optional.empty(), + FakeExecution() + ) + screenOnCoordinator.onScreenTurningOn(runnable) + + // Should be called when only keyguard drawn + verify(runnable).run() + } + + @Test + fun testWakeAndUnlockDelaysRunnable() { + // GIVEN wakeAndUnlocking has been set to true + screenOnCoordinator.wakeAndUnlocking = true + + // WHEN the screen turns on and two tasks have completed + screenOnCoordinator.onScreenTurningOn(runnable) + onUnfoldOverlayReady() + onFoldAodReady() + + // THEN the runnable should not have run yet + verify(runnable, never()).run() + + // WHEN the value of wakeAndUnlocking changes + screenOnCoordinator.wakeAndUnlocking = false + + // THEN the runnable should have run, as it is the last task to complete + verify(runnable).run() + } + + private fun onUnfoldOverlayReady() { + verify(unfoldAnimation).onScreenTurningOn(capture(readyCaptor)) + readyCaptor.getValue().run() + } + + private fun onFoldAodReady() { + verify(foldAodAnimationController).onScreenTurningOn(capture(readyCaptor)) + readyCaptor.getValue().run() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index 4f3266d0db44..40632a85d722 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -44,8 +44,10 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.SmartReplyController; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; +import org.mockito.Mockito; import java.io.FileInputStream; import java.io.IOException; @@ -120,11 +122,15 @@ public abstract class SysuiTestCase { TestableLooper.get(this).processAllMessages(); } disallowTestableLooperAsMainThread(); - SystemUIFactory.cleanup(); mContext.cleanUpReceivers(this.getClass().getSimpleName()); mFakeBroadcastDispatcher.cleanUpReceivers(this.getClass().getSimpleName()); } + @AfterClass + public static void mockitoTearDown() { + Mockito.framework().clearInlineMocks(); + } + /** * Tests are run on the TestableLooper; however, there are parts of SystemUI that assert that * the code is run from the main looper. Therefore, we allow the TestableLooper to pass these 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 27fcb11b8dfc..9827d21e29b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -34,7 +34,6 @@ import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.os.PowerManager; import android.os.PowerManager.WakeLock; -import android.os.RemoteException; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -42,10 +41,10 @@ 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; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; @@ -58,9 +57,6 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.unfold.FoldAodAnimationController; -import com.android.systemui.unfold.SysUIUnfoldComponent; -import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; @@ -69,13 +65,9 @@ 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.MockitoAnnotations; -import java.util.Optional; -import java.util.function.Function; - @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest @@ -95,36 +87,25 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock NavigationModeController mNavigationModeController; private @Mock KeyguardDisplayManager mKeyguardDisplayManager; private @Mock DozeParameters mDozeParameters; - private @Mock SysUIUnfoldComponent mSysUIUnfoldComponent; - private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation; private @Mock SysuiStatusBarStateController mStatusBarStateController; private @Mock KeyguardStateController mKeyguardStateController; private @Mock NotificationShadeDepthController mNotificationShadeDepthController; private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock ScreenOffAnimationController mScreenOffAnimationController; - private @Mock FoldAodAnimationController mFoldAodAnimationController; - private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback; private @Mock InteractionJankMonitor mInteractionJankMonitor; + private @Mock ScreenOnCoordinator mScreenOnCoordinator; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); - private Optional<SysUIUnfoldComponent> mSysUiUnfoldComponentOptional; - private FalsingCollectorFake mFalsingCollector; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mFalsingCollector = new FalsingCollectorFake(); - mSysUiUnfoldComponentOptional = Optional.of(mSysUIUnfoldComponent); when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager); when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class)); - when(mSysUIUnfoldComponent.getUnfoldLightRevealOverlayAnimation()) - .thenReturn(mUnfoldAnimation); - when(mSysUIUnfoldComponent.getFoldAodAnimationController()) - .thenReturn(mFoldAodAnimationController); - when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true); when(mInteractionJankMonitor.end(anyInt())).thenReturn(true); @@ -151,33 +132,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void testUnfoldTransitionEnabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() - throws RemoteException { - mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback); - TestableLooper.get(this).processAllMessages(); - onUnfoldOverlayReady(); - onFoldAodReady(); - - // Should be called when both unfold overlay and keyguard drawn ready - verify(mKeyguardDrawnCallback).onDrawn(); - } - - @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() - throws RemoteException { - mSysUiUnfoldComponentOptional = Optional.empty(); - createAndStartViewMediator(); - - mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback); - TestableLooper.get(this).processAllMessages(); - - // Should be called when only keyguard drawn - verify(mKeyguardDrawnCallback).onDrawn(); - } - - @Test public void testIsAnimatingScreenOff() { when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true); @@ -219,20 +173,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null); } - private void onUnfoldOverlayReady() { - ArgumentCaptor<Runnable> overlayReadyCaptor = ArgumentCaptor.forClass(Runnable.class); - verify(mUnfoldAnimation).onScreenTurningOn(overlayReadyCaptor.capture()); - overlayReadyCaptor.getValue().run(); - TestableLooper.get(this).processAllMessages(); - } - - private void onFoldAodReady() { - ArgumentCaptor<Runnable> ready = ArgumentCaptor.forClass(Runnable.class); - verify(mFoldAodAnimationController).onScreenTurningOn(ready.capture()); - ready.getValue().run(); - TestableLooper.get(this).processAllMessages(); - } - private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, @@ -251,12 +191,12 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mNavigationModeController, mKeyguardDisplayManager, mDozeParameters, - mSysUiUnfoldComponentOptional, mStatusBarStateController, mKeyguardStateController, () -> mKeyguardUnlockAnimationController, mScreenOffAnimationController, () -> mNotificationShadeDepthController, + mScreenOnCoordinator, mInteractionJankMonitor); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java index 2d1b25806dcc..53d0dd8f0631 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java @@ -57,15 +57,16 @@ public class ScreenLifecycleTest extends SysuiTestCase { @Test public void screenTurningOn() throws Exception { - mScreen.dispatchScreenTurningOn(); + Runnable onDrawn = () -> {}; + mScreen.dispatchScreenTurningOn(onDrawn); assertEquals(ScreenLifecycle.SCREEN_TURNING_ON, mScreen.getScreenState()); - verify(mScreenObserverMock).onScreenTurningOn(); + verify(mScreenObserverMock).onScreenTurningOn(onDrawn); } @Test public void screenTurnedOn() throws Exception { - mScreen.dispatchScreenTurningOn(); + mScreen.dispatchScreenTurningOn(null); mScreen.dispatchScreenTurnedOn(); assertEquals(ScreenLifecycle.SCREEN_ON, mScreen.getScreenState()); @@ -74,7 +75,7 @@ public class ScreenLifecycleTest extends SysuiTestCase { @Test public void screenTurningOff() throws Exception { - mScreen.dispatchScreenTurningOn(); + mScreen.dispatchScreenTurningOn(null); mScreen.dispatchScreenTurnedOn(); mScreen.dispatchScreenTurningOff(); @@ -84,7 +85,7 @@ public class ScreenLifecycleTest extends SysuiTestCase { @Test public void screenTurnedOff() throws Exception { - mScreen.dispatchScreenTurningOn(); + mScreen.dispatchScreenTurningOn(null); mScreen.dispatchScreenTurnedOn(); mScreen.dispatchScreenTurningOff(); mScreen.dispatchScreenTurnedOff(); @@ -97,4 +98,4 @@ public class ScreenLifecycleTest extends SysuiTestCase { public void dump() throws Exception { mScreen.dump(null, new PrintWriter(new ByteArrayOutputStream()), new String[0]); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt index b74ba2646ba3..927ca7a34cf8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt @@ -19,14 +19,18 @@ package com.android.systemui.media.taptotransfer.common import android.content.Context import android.graphics.drawable.Drawable import android.graphics.drawable.Icon +import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.reset @@ -50,24 +54,24 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { @Test fun displayChip_chipAdded() { - controllerCommon.displayChip(MediaTttChipState(appIconDrawable)) + controllerCommon.displayChip(getState()) verify(windowManager).addView(any(), any()) } @Test fun displayChip_twice_chipNotAddedTwice() { - controllerCommon.displayChip(MediaTttChipState(appIconDrawable)) + controllerCommon.displayChip(getState()) reset(windowManager) - controllerCommon.displayChip(MediaTttChipState(appIconDrawable)) + controllerCommon.displayChip(getState()) verify(windowManager, never()).addView(any(), any()) } @Test fun removeChip_chipRemoved() { // First, add the chip - controllerCommon.displayChip(MediaTttChipState(appIconDrawable)) + controllerCommon.displayChip(getState()) // Then, remove it controllerCommon.removeChip() @@ -82,6 +86,29 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) } + @Test + fun setIcon_viewHasIconAndContentDescription() { + controllerCommon.displayChip(getState()) + val chipView = getChipView() + val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) + val contentDescription = "test description" + + controllerCommon.setIcon(MediaTttChipState(drawable, contentDescription), chipView) + + assertThat(chipView.getAppIconView().drawable).isEqualTo(drawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(contentDescription) + } + + private fun getState() = MediaTttChipState(appIconDrawable, APP_ICON_CONTENT_DESCRIPTION) + + private fun getChipView(): ViewGroup { + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + verify(windowManager).addView(viewCaptor.capture(), any()) + return viewCaptor.value as ViewGroup + } + + private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) + inner class TestControllerCommon( context: Context, windowManager: WindowManager @@ -92,3 +119,5 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { } } } + +private const val APP_ICON_CONTENT_DESCRIPTION = "Content description" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 2ff472fc7abe..afaab807c74c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.media.taptotransfer.receiver -import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.view.View import android.view.ViewGroup @@ -50,10 +49,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Test fun displayChip_chipContainsIcon() { val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) + val contentDescription = "Test description" - controllerReceiver.displayChip(ChipStateReceiver(drawable)) + controllerReceiver.displayChip(ChipStateReceiver(drawable, contentDescription)) - assertThat(getChipView().getAppIconDrawable()).isEqualTo(drawable) + assertThat(getChipView().getAppIconView().drawable).isEqualTo(drawable) + assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(contentDescription) } private fun getChipView(): ViewGroup { @@ -62,6 +63,5 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { return viewCaptor.value as ViewGroup } - private fun ViewGroup.getAppIconDrawable(): Drawable = - (this.requireViewById<ImageView>(R.id.app_icon)).drawable + private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 028ec55ce28e..caef5b901e0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -70,7 +70,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(moveCloserToTransfer()) val chipView = getChipView() - assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) assertThat(chipView.getChipText()).contains(DEVICE_NAME) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -85,7 +86,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { // Assert we're still in the loading state val chipView = getChipView() - assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) assertThat(chipView.getChipText()).contains(DEVICE_NAME) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -155,7 +157,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { controllerSender.displayChip(transferSucceeded()) val chipView = getChipView() - assertThat(chipView.getAppIconDrawable()).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) assertThat(chipView.getChipText()).contains(DEVICE_NAME) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) } @@ -220,8 +223,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE) } - private fun LinearLayout.getAppIconDrawable(): Drawable = - (this.requireViewById<ImageView>(R.id.app_icon)).drawable + private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon) private fun LinearLayout.getChipText(): String = (this.requireViewById<TextView>(R.id.text)).text as String @@ -238,20 +240,22 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } /** Helper method providing default parameters to not clutter up the tests. */ - private fun moveCloserToTransfer() = MoveCloserToTransfer(appIconDrawable, DEVICE_NAME) + private fun moveCloserToTransfer() = + MoveCloserToTransfer(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferInitiated( future: Future<Runnable?> = TEST_FUTURE - ) = TransferInitiated(appIconDrawable, DEVICE_NAME, future) + ) = TransferInitiated(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, future) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferSucceeded( undoRunnable: Runnable? = null - ) = TransferSucceeded(appIconDrawable, DEVICE_NAME, undoRunnable) + ) = TransferSucceeded(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable) } private const val DEVICE_NAME = "My Tablet" +private const val APP_ICON_CONTENT_DESC = "Content description" // Use a settable future that hasn't yet been set so that we don't immediately switch to the success // state. private val TEST_FUTURE: SettableFuture<Runnable?> = SettableFuture.create() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index af33dafb6107..968b12afa68f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.qs.tiles.BluetoothTile import com.android.systemui.qs.tiles.CameraToggleTile import com.android.systemui.qs.tiles.CastTile import com.android.systemui.qs.tiles.CellularTile +import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile import com.android.systemui.qs.tiles.DataSaverTile import com.android.systemui.qs.tiles.DeviceControlsTile @@ -91,7 +92,8 @@ private val specMap = mapOf( "wallet" to QuickAccessWalletTile::class.java, "qr_code_scanner" to QRCodeScannerTile::class.java, "onehanded" to OneHandedModeTile::class.java, - "fgsmanager" to FgsManagerTile::class.java + "fgsmanager" to FgsManagerTile::class.java, + "color_correction" to ColorCorrectionTile::class.java ) @RunWith(AndroidTestingRunner::class) @@ -132,6 +134,7 @@ class QSFactoryImplTest : SysuiTestCase() { @Mock private lateinit var qrCodeScannerTile: QRCodeScannerTile @Mock private lateinit var oneHandedModeTile: OneHandedModeTile @Mock private lateinit var fgsManagerTile: FgsManagerTile + @Mock private lateinit var colorCorrectionTile: ColorCorrectionTile private lateinit var factory: QSFactoryImpl @@ -175,7 +178,8 @@ class QSFactoryImplTest : SysuiTestCase() { { quickAccessWalletTile }, { qrCodeScannerTile }, { oneHandedModeTile }, - { fgsManagerTile } + { fgsManagerTile }, + { colorCorrectionTile } ) // When adding/removing tiles, fix also [specMap] } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java new file mode 100644 index 000000000000..debe41c756bd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.os.Handler; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +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.classifier.FalsingManagerFake; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.SecureSettings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class ColorCorrectionTileTest extends SysuiTestCase { + + @Mock + private QSTileHost mHost; + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private ActivityStarter mActivityStarter; + @Mock + private QSLogger mQSLogger; + @Mock + private UiEventLogger mUiEventLogger; + @Mock + private UserTracker mUserTracker; + + private TestableLooper mTestableLooper; + private SecureSettings mSecureSettings; + private ColorCorrectionTile mTile; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mSecureSettings = new FakeSettings(); + mTestableLooper = TestableLooper.get(this); + + when(mHost.getContext()).thenReturn(mContext); + when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); + + mTile = new ColorCorrectionTile( + mHost, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQSLogger, + mUserTracker, + mSecureSettings + ); + + mTile.initialize(); + mTestableLooper.processAllMessages(); + } + + @Test + public void longClick_expectedAction() { + final ArgumentCaptor<Intent> IntentCaptor = ArgumentCaptor.forClass(Intent.class); + + mTile.longClick(/* view= */ null); + mTestableLooper.processAllMessages(); + + verify(mActivityStarter).postStartActivityDismissingKeyguard(IntentCaptor.capture(), + anyInt(), any()); + assertThat(IntentCaptor.getValue().getAction()).isEqualTo( + Settings.ACTION_COLOR_CORRECTION_SETTINGS); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java new file mode 100644 index 000000000000..55c51b255ac7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java @@ -0,0 +1,212 @@ +/* + * 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.qs.tiles; + +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; + +import static junit.framework.TestCase.assertEquals; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.hardware.SensorPrivacyManager; +import android.os.Handler; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController; +import com.android.systemui.statusbar.policy.RotationLockController; +import com.android.systemui.statusbar.policy.RotationLockControllerImpl; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.wrapper.RotationPolicyWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class RotationLockTileTest extends SysuiTestCase { + + private static final String PACKAGE_NAME = "package_name"; + private static final String[] DEFAULT_SETTINGS = new String[]{ + "0:0", + "1:2" + }; + + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityStarter mActivityStarter; + @Mock + private QSTileHost mHost; + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private QSLogger mQSLogger; + @Mock + private SensorPrivacyManager mPrivacyManager; + @Mock + private BatteryController mBatteryController; + @Mock + DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; + @Mock + RotationPolicyWrapper mRotationPolicyWrapper; + + private RotationLockController mController; + private TestableLooper mTestableLooper; + private RotationLockTile mLockTile; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + + when(mHost.getContext()).thenReturn(mContext); + when(mHost.getUserContext()).thenReturn(mContext); + + mController = new RotationLockControllerImpl(mRotationPolicyWrapper, + mDeviceStateRotationLockSettingController, DEFAULT_SETTINGS); + + mLockTile = new RotationLockTile( + mHost, + mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), + new FalsingManagerFake(), + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQSLogger, + mController, + mPrivacyManager, + mBatteryController, + new FakeSettings() + ); + + mLockTile.initialize(); + + // We are not setting the mocks to listening, so we trigger a first refresh state to + // set the initial state + mLockTile.refreshState(); + + mTestableLooper.processAllMessages(); + + mContext.setMockPackageManager(mPackageManager); + doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName(); + doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission( + Manifest.permission.CAMERA, PACKAGE_NAME); + + when(mBatteryController.isPowerSave()).thenReturn(false); + when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(false); + enableAutoRotation(); + enableCameraBasedRotation(); + + mLockTile.refreshState(); + mTestableLooper.processAllMessages(); + } + + @Test + public void testSecondaryString_cameraRotateOn_returnsFaceBased() { + assertEquals(mContext.getString(R.string.rotation_lock_camera_rotation_on), + mLockTile.getState().secondaryLabel.toString()); + } + + @Test + public void testSecondaryString_rotateOff_isEmpty() { + disableAutoRotation(); + + mLockTile.refreshState(); + mTestableLooper.processAllMessages(); + + assertEquals("", mLockTile.getState().secondaryLabel.toString()); + } + + @Test + public void testSecondaryString_cameraRotateOff_isEmpty() { + disableCameraBasedRotation(); + + mLockTile.refreshState(); + mTestableLooper.processAllMessages(); + + assertEquals("", mLockTile.getState().secondaryLabel.toString()); + } + + @Test + public void testSecondaryString_powerSaveEnabled_isEmpty() { + when(mBatteryController.isPowerSave()).thenReturn(true); + + mLockTile.refreshState(); + mTestableLooper.processAllMessages(); + + assertEquals("", mLockTile.getState().secondaryLabel.toString()); + } + + @Test + public void testSecondaryString_cameraDisabled_isEmpty() { + when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(true); + + mLockTile.refreshState(); + mTestableLooper.processAllMessages(); + + assertEquals("", mLockTile.getState().secondaryLabel.toString()); + } + + @Test + public void testSecondaryString_noCameraPermission_isEmpty() { + doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( + Manifest.permission.CAMERA, PACKAGE_NAME); + + mLockTile.refreshState(); + mTestableLooper.processAllMessages(); + + assertEquals("", mLockTile.getState().secondaryLabel.toString()); + } + + private void enableAutoRotation() { + when(mRotationPolicyWrapper.isRotationLocked()).thenReturn(false); + } + + private void disableAutoRotation() { + when(mRotationPolicyWrapper.isRotationLocked()).thenReturn(true); + } + + private void enableCameraBasedRotation() { + when(mRotationPolicyWrapper.isCameraRotationEnabled()).thenReturn(true); + } + + private void disableCameraBasedRotation() { + when(mRotationPolicyWrapper.isCameraRotationEnabled()).thenReturn(false); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java index 9bf877584679..8a388479c0e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -86,6 +87,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { super(mock(HeadsUpManagerLogger.class)); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; + mHandler.removeCallbacksAndMessages(null); mHandler = mTestHandler; } @@ -145,6 +147,11 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { mAlertingNotificationManager = createAlertingNotificationManager(); } + @After + public void tearDown() { + mTestHandler.removeCallbacksAndMessages(null); + } + @Test public void testShowNotification_addsEntry() { mAlertingNotificationManager.showNotification(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index e427d53306ea..7f357324bed0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -81,6 +81,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; +import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -154,6 +155,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private IActivityManager mIActivityManager; @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock + private ScreenLifecycle mScreenLifecycle; @Captor private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener; @Captor @@ -195,7 +198,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { R.string.do_financed_disclosure_with_name, ORGANIZATION_NAME); when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); - when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true); + when(mScreenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON); when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true); when(mIndicationArea.findViewById(R.id.keyguard_indication_text_bottom)) @@ -225,8 +228,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController = new KeyguardIndicationController(mContext, mWakeLockBuilder, mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, - mUserManager, mExecutor, mFalsingManager, mLockPatternUtils, mIActivityManager, - mKeyguardBypassController); + mUserManager, mExecutor, mFalsingManager, mLockPatternUtils, mScreenLifecycle, + mIActivityManager, mKeyguardBypassController); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); @@ -702,7 +705,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // GIVEN state of showing message when keyguard screen is on when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false); - when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true); // GIVEN fingerprint is also running (not udfps) when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt index c74437f5ad94..3a60c049b3f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt @@ -4,6 +4,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper @@ -29,6 +30,7 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { @Mock lateinit var notificationShadeWindowViewController: NotificationShadeWindowViewController @Mock lateinit var notificationListContainer: NotificationListContainer @Mock lateinit var headsUpManager: HeadsUpManagerPhone + @Mock lateinit var jankMonitor: InteractionJankMonitor private lateinit var notificationTestHelper: NotificationTestHelper private lateinit var notification: ExpandableNotificationRow @@ -49,7 +51,8 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { notificationShadeWindowViewController, notificationListContainer, headsUpManager, - notification + notification, + jankMonitor ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java index a3569e4060d1..a11b46df1218 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java @@ -19,8 +19,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +47,8 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -52,6 +57,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -75,6 +82,9 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { @Mock private NodeController mHeaderController; private NotificationEntry mEntry; + private final FakeSystemClock mClock = new FakeSystemClock(); + private final FakeExecutor mExecutor = new FakeExecutor(mClock); + private final ArrayList<NotificationEntry> mHuns = new ArrayList(); @Before public void setUp() { @@ -85,7 +95,8 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { mHeadsUpViewBinder, mNotificationInterruptStateProvider, mRemoteInputManager, - mHeaderController); + mHeaderController, + mExecutor); mCoordinator.attach(mNotifPipeline); @@ -105,6 +116,16 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { notifLifetimeExtenderCaptor.capture()); verify(mHeadsUpManager).addListener(headsUpChangedListenerCaptor.capture()); + given(mHeadsUpManager.getAllEntries()).willAnswer(i -> mHuns.stream()); + given(mHeadsUpManager.isAlerting(anyString())).willAnswer(i -> { + String key = i.getArgument(0); + for (NotificationEntry entry : mHuns) { + if (entry.getKey().equals(key)) return true; + } + return false; + }); + when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L); + mCollectionListener = notifCollectionCaptor.getValue(); mNotifPromoter = notifPromoterCaptor.getValue(); mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue(); @@ -116,63 +137,64 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { } @Test + public void testCancelStickyNotification() { + when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); + addHUN(mEntry); + when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L); + assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, 0)); + mClock.advanceTime(1000L); + mExecutor.runAllReady(); + verify(mHeadsUpManager, times(1)) + .removeNotification(anyString(), eq(true)); + } + + @Test + public void testCancelUpdatedStickyNotification() { + when(mHeadsUpManager.isSticky(anyString())).thenReturn(true); + addHUN(mEntry); + when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L); + assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, 0)); + mClock.advanceTime(1000L); + mExecutor.runAllReady(); + verify(mHeadsUpManager, times(0)) + .removeNotification(anyString(), eq(true)); + } + + @Test public void testPromotesCurrentHUN() { // GIVEN the current HUN is set to mEntry - setCurrentHUN(mEntry); + addHUN(mEntry); // THEN only promote the current HUN, mEntry assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)); - assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder().build())); + assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder() + .setPkg("test-package2") + .build())); } @Test public void testIncludeInSectionCurrentHUN() { // GIVEN the current HUN is set to mEntry - setCurrentHUN(mEntry); + addHUN(mEntry); // THEN only section the current HUN, mEntry assertTrue(mNotifSectioner.isInSection(mEntry)); - assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder().build())); + assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder() + .setPkg("test-package") + .build())); } @Test public void testLifetimeExtendsCurrentHUN() { // GIVEN there is a HUN, mEntry - setCurrentHUN(mEntry); + addHUN(mEntry); // THEN only the current HUN, mEntry, should be lifetimeExtended assertTrue(mNotifLifetimeExtender.shouldExtendLifetime(mEntry, /* cancellationReason */ 0)); assertFalse(mNotifLifetimeExtender.shouldExtendLifetime( - new NotificationEntryBuilder().build(), /* cancellationReason */ 0)); - } - - @Test - public void testLifetimeExtensionEndsOnNewHUN() { - // GIVEN there was a HUN that was lifetime extended - setCurrentHUN(mEntry); - assertTrue(mNotifLifetimeExtender.shouldExtendLifetime( - mEntry, /* cancellation reason */ 0)); - - // WHEN there's a new HUN - NotificationEntry newHUN = new NotificationEntryBuilder().build(); - setCurrentHUN(newHUN); - - // THEN the old entry's lifetime extension should be cancelled - verify(mEndLifetimeExtension).onEndLifetimeExtension(mNotifLifetimeExtender, mEntry); - } - - @Test - public void testLifetimeExtensionEndsOnNoHUNs() { - // GIVEN there was a HUN that was lifetime extended - setCurrentHUN(mEntry); - assertTrue(mNotifLifetimeExtender.shouldExtendLifetime( - mEntry, /* cancellation reason */ 0)); - - // WHEN there's no longer a HUN - setCurrentHUN(null); - - // THEN the old entry's lifetime extension should be cancelled - verify(mEndLifetimeExtension).onEndLifetimeExtension(mNotifLifetimeExtender, mEntry); + new NotificationEntryBuilder() + .setPkg("test-package") + .build(), /* cancellationReason */ 0)); } @Test @@ -208,7 +230,7 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { @Test public void testOnEntryRemovedRemovesHeadsUpNotification() { // GIVEN the current HUN is mEntry - setCurrentHUN(mEntry); + addHUN(mEntry); // WHEN mEntry is removed from the notification collection mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0); @@ -218,12 +240,9 @@ public class HeadsUpCoordinatorTest extends SysuiTestCase { verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false); } - private void setCurrentHUN(NotificationEntry entry) { + private void addHUN(NotificationEntry entry) { + mHuns.add(entry); when(mHeadsUpManager.getTopEntry()).thenReturn(entry); - when(mHeadsUpManager.isAlerting(any())).thenReturn(false); - if (entry != null) { - when(mHeadsUpManager.isAlerting(entry.getKey())).thenReturn(true); - } mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, entry != null); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java index c5dc2b4d4f03..3ddff4997429 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java @@ -120,7 +120,7 @@ public final class MediaCoordinatorTest extends SysuiTestCase { private NotifFilter captureFilter(MediaCoordinator coordinator) { ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); coordinator.attach(mNotifPipeline); - verify(mNotifPipeline).addFinalizeFilter(filterCaptor.capture()); + verify(mNotifPipeline).addPreGroupFilter(filterCaptor.capture()); return filterCaptor.getValue(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java index 2dfb9fc674ba..429d2ed36cc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java @@ -60,6 +60,7 @@ import com.android.systemui.util.time.FakeSystemClock; import com.google.android.collect.Lists; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -131,6 +132,11 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { verify(mNotifPipeline, never()).addCollectionListener(any()); } + @After + public void tearDown() { + mLogger.mHandler.removeCallbacksAndMessages(null); + } + @Test public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception { NotificationVisibility[] newlyVisibleKeys = { @@ -281,6 +287,7 @@ public class NotificationLoggerLegacyTest extends SysuiTestCase { mNotificationPanelLoggerFake ); mBarService = barService; + mHandler.removeCallbacksAndMessages(null); // Make this on the current thread so we can wait for it during tests. mHandler = Handler.createAsync(Looper.myLooper()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 4d861f96a943..b69bd8dfca9c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -60,6 +60,7 @@ import com.android.systemui.util.time.FakeSystemClock; import com.google.android.collect.Lists; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -132,6 +133,11 @@ public class NotificationLoggerTest extends SysuiTestCase { verify(mNotifPipeline).addCollectionListener(any()); } + @After + public void tearDown() { + mLogger.mHandler.removeCallbacksAndMessages(null); + } + @Test public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception { NotificationVisibility[] newlyVisibleKeys = { @@ -282,6 +288,7 @@ public class NotificationLoggerTest extends SysuiTestCase { mNotificationPanelLoggerFake ); mBarService = barService; + mHandler.removeCallbacksAndMessages(null); // Make this on the current thread so we can wait for it during tests. mHandler = Handler.createAsync(Looper.myLooper()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index f3eece84e34f..e4721b13332c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -36,6 +36,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.graphics.drawable.Icon; +import android.os.Handler; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; @@ -139,6 +140,8 @@ public class NotificationTestHelper { mock(NotificationGroupManagerLegacy.class), mock(ConfigurationControllerImpl.class) ); + mHeadsUpManager.mHandler.removeCallbacksAndMessages(null); + mHeadsUpManager.mHandler = new Handler(mTestLooper.getLooper()); mGroupMembershipManager.setHeadsUpManager(mHeadsUpManager); mIconManager = new IconManager( mock(CommonNotifCollection.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index 3c84c0193aac..d3c1dc9db218 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -32,7 +32,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -44,7 +43,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.HashSet; @@ -59,8 +57,6 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private Runnable mRoundnessCallback = mock(Runnable.class); private ExpandableNotificationRow mFirst; private ExpandableNotificationRow mSecond; - @Mock - private FeatureFlags mFeatureFlags; private float mSmallRadiusRatio; @Before @@ -70,8 +66,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small) / resources.getDimension(R.dimen.notification_corner_radius); mRoundnessManager = new NotificationRoundnessManager( - new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext), - mFeatureFlags); + new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext)); allowTestableLooperAsMainThread(); NotificationTestHelper testHelper = new NotificationTestHelper( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index 276f246d89da..4d2c0c306c00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -32,6 +32,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -46,9 +47,7 @@ import android.view.ViewGroup; import androidx.test.filters.SmallTest; -import com.android.systemui.ActivityStarterDelegate; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; @@ -56,7 +55,6 @@ import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; -import com.android.systemui.statusbar.notification.people.PeopleHubViewAdapter; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; @@ -66,6 +64,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -266,8 +265,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { // THEN the header is first removed from the transient parent before being added to the // NSSL. - verify(transientParent).removeTransientView(silentHeaderView); - verify(mNssl).addView(silentHeaderView, 1); + final InOrder inOrder = inOrder(silentHeaderView, mNssl); + inOrder.verify(silentHeaderView).removeFromTransientContainer(); + inOrder.verify(mNssl).addView(eq(silentHeaderView), eq(1)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 7194c6620e12..4cc1be696637 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -38,6 +38,7 @@ import android.view.LayoutInflater; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; @@ -135,6 +136,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private VisualStabilityManager mVisualStabilityManager; @Mock private ShadeController mShadeController; + @Mock private InteractionJankMonitor mJankMonitor; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; @@ -188,7 +190,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mLayoutInflater, mRemoteInputManager, mVisualStabilityManager, - mShadeController + mShadeController, + mJankMonitor ); when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt new file mode 100644 index 000000000000..649dc235f398 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt @@ -0,0 +1,130 @@ +/* + * 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.statusbar.phone + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.FoldStateListener.OnFoldStateChangeListener +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.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FoldStateListenerTest : SysuiTestCase() { + + @Mock + private lateinit var listener: OnFoldStateChangeListener + private lateinit var sut: FoldStateListener + + @Before + fun setUp() { + initMocks(this) + setFoldedStates(DEVICE_STATE_FOLDED) + setGoToSleepStates(DEVICE_STATE_FOLDED) + sut = FoldStateListener(mContext, listener) + } + + @Test + fun onStateChanged_stateFolded_notifiesWithFoldedAndGoingToSleep() { + sut.onStateChanged(DEVICE_STATE_FOLDED) + + verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) + } + + @Test + fun onStateChanged_stateHalfFolded_notifiesWithNotFoldedAndNotGoingToSleep() { + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + + verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateUnfolded_notifiesWithNotFoldedAndNotGoingToSleep() { + sut.onStateChanged(DEVICE_STATE_UNFOLDED) + + verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateUnfoldedThenHalfFolded_notifiesOnce() { + sut.onStateChanged(DEVICE_STATE_UNFOLDED) + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + + verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateHalfFoldedThenUnfolded_notifiesOnce() { + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onStateChanged(DEVICE_STATE_UNFOLDED) + + verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + @Test + fun onStateChanged_stateHalfFoldedThenFolded_notifiesTwice() { + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onStateChanged(DEVICE_STATE_FOLDED) + + val inOrder = Mockito.inOrder(listener) + inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) + } + + @Test + fun onStateChanged_stateFoldedThenHalfFolded_notifiesTwice() { + sut.onStateChanged(DEVICE_STATE_FOLDED) + sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + + val inOrder = Mockito.inOrder(listener) + inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) + inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) + } + + private fun setGoToSleepStates(vararg states: Int) { + mContext.orCreateTestableResources.addOverride( + R.array.config_deviceStatesOnWhichToSleep, + states + ) + } + + private fun setFoldedStates(vararg states: Int) { + mContext.orCreateTestableResources.addOverride( + R.array.config_foldedDeviceStates, + states + ) + } + + companion object { + private const val DEVICE_STATE_FOLDED = 123 + private const val DEVICE_STATE_HALF_FOLDED = 456 + private const val DEVICE_STATE_UNFOLDED = 789 + + private const val FOLDED = true + private const val NOT_FOLDED = false + + private const val WILL_GO_TO_SLEEP = true + private const val WILL_NOT_SLEEP = false + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index 0f419c768430..e8b9c7b4d289 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -115,9 +116,15 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mConfigurationController ); super.setUp(); + mHeadsUpManager.mHandler.removeCallbacksAndMessages(null); mHeadsUpManager.mHandler = mTestHandler; } + @After + public void tearDown() { + mTestHandler.removeCallbacksAndMessages(null); + } + @Test public void testSnooze() { mHeadsUpManager.showNotification(mEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index e5f2aa7a93c7..f391eff11ed2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; @@ -43,7 +44,6 @@ import android.widget.FrameLayout; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardHostViewController; -import com.android.keyguard.KeyguardRootViewController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; @@ -64,7 +64,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import org.mockito.stubbing.Answer; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -92,13 +91,10 @@ public class KeyguardBouncerTest extends SysuiTestCase { @Mock private KeyguardSecurityModel mKeyguardSecurityModel; @Mock - private KeyguardRootViewController mRootViewController; - @Mock - private ViewGroup mRootView; - @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent; + private ViewGroup mContainer; @Rule public MockitoRule mRule = MockitoJUnit.rule(); private Integer mRootVisibility = View.INVISIBLE; @@ -107,32 +103,22 @@ public class KeyguardBouncerTest extends SysuiTestCase { @Before public void setup() { allowTestableLooperAsMainThread(); - mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor); - mDependency.injectMockDependency(KeyguardStateController.class); - when(mRootView.getVisibility()).thenAnswer((Answer<Integer>) invocation -> mRootVisibility); - doAnswer(invocation -> { - mRootVisibility = invocation.getArgument(0); - return null; - }).when(mRootView).setVisibility(anyInt()); when(mKeyguardSecurityModel.getSecurityMode(anyInt())) .thenReturn(KeyguardSecurityModel.SecurityMode.None); DejankUtils.setImmediate(true); - when(mKeyguardBouncerComponentFactory.create()).thenReturn(mKeyguardBouncerComponent); + + mContainer = spy(new FrameLayout(getContext())); + when(mKeyguardBouncerComponentFactory.create(mContainer)).thenReturn( + mKeyguardBouncerComponent); when(mKeyguardBouncerComponent.getKeyguardHostViewController()) .thenReturn(mKeyguardHostViewController); - when(mKeyguardBouncerComponent.getKeyguardRootViewController()) - .thenReturn(mRootViewController); - - when(mRootViewController.getView()).thenReturn(mRootView); - when(mRootView.getResources()).thenReturn(mContext.getResources()); - final ViewGroup container = new FrameLayout(getContext()); mBouncer = new KeyguardBouncer.Factory(getContext(), mViewMediatorCallback, mDismissCallbackRegistry, mFalsingCollector, mKeyguardStateController, mKeyguardUpdateMonitor, mKeyguardBypassController, mHandler, mKeyguardSecurityModel, mKeyguardBouncerComponentFactory) - .create(container, mExpansionCallback); + .create(mContainer, mExpansionCallback); } @Test @@ -233,7 +219,7 @@ public class KeyguardBouncerTest extends SysuiTestCase { mBouncer.setExpansion(0); verify(mKeyguardHostViewController).onResume(); - verify(mRootView).announceForAccessibility(any()); + verify(mContainer).announceForAccessibility(any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 11826954baee..1827c7f0c0a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -62,6 +62,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private float mPanelExpansion; private int mKeyguardStatusBarHeaderHeight; private int mKeyguardStatusHeight; + private int mUserSwitchHeight; private float mDark; private float mQsExpansion; private int mCutoutTopInset = 0; @@ -264,8 +265,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { @Test public void clockPositionedDependingOnMarginInSplitShade() { - when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) - .thenReturn(400); + setSplitShadeTopMargin(400); mClockPositionAlgorithm.loadDimens(mResources); givenLockScreen(); mIsSplitShade = true; @@ -291,6 +291,32 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { } @Test + public void notifPaddingAccountsForMultiUserSwitcherInSplitShade() { + setSplitShadeTopMargin(100); + mUserSwitchHeight = 150; + mClockPositionAlgorithm.loadDimens(mResources); + givenLockScreen(); + mIsSplitShade = true; + // WHEN the position algorithm is run + positionClock(); + // THEN the notif padding is split shade top margin + user switch height + assertThat(mClockPosition.stackScrollerPadding).isEqualTo(250); + } + + @Test + public void clockDoesntAccountForMultiUserSwitcherInSplitShade() { + setSplitShadeTopMargin(100); + mUserSwitchHeight = 150; + mClockPositionAlgorithm.loadDimens(mResources); + givenLockScreen(); + mIsSplitShade = true; + // WHEN the position algorithm is run + positionClock(); + // THEN clockY = split shade top margin + assertThat(mClockPosition.clockY).isEqualTo(100); + } + + @Test public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() { givenLockScreen(); mIsSplitShade = true; @@ -495,6 +521,11 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset); } + private void setSplitShadeTopMargin(int value) { + when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) + .thenReturn(value); + } + private void givenHighestBurnInOffset() { when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg()); } @@ -529,7 +560,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { mKeyguardStatusBarHeaderHeight, mPanelExpansion, mKeyguardStatusHeight, - 0 /* userSwitchHeight */, + mUserSwitchHeight, 0 /* userSwitchPreferredY */, mDark, ZERO_DRAG, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index b717d28b24ff..9898b4b2fdbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -51,6 +51,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.wm.shell.bubbles.Bubbles; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -111,6 +112,11 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { mHeadsUpManager.addListener(mGroupAlertTransferHelper); } + @After + public void tearDown() { + mHeadsUpManager.mHandler.removeCallbacksAndMessages(null); + } + private void mockHasHeadsUpContentView(NotificationEntry entry, boolean hasHeadsUpContentView) { RowContentBindParams params = new RowContentBindParams(); 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 1cd9b9e77423..5ada6d4239d1 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 @@ -141,6 +141,7 @@ import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; @@ -148,6 +149,7 @@ import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.wallet.controller.QuickAccessWalletController; import com.android.wm.shell.animation.FlingAnimationUtils; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -358,6 +360,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private NotificationsQSContainerController mNotificationsQSContainerController; @Mock private QsFrameTranslateController mQsFrameTranslateController; + @Mock + private StatusBarWindowStateController mStatusBarWindowStateController; private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; @@ -366,6 +370,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; private FalsingManagerFake mFalsingManager = new FalsingManagerFake(); private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + private Handler mMainHandler; @Before public void setup() { @@ -483,15 +488,19 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .thenReturn(true); reset(mView); + mMainHandler = new Handler(Looper.getMainLooper()); + mNotificationPanelViewController = new NotificationPanelViewController(mView, mResources, - new Handler(Looper.getMainLooper()), + mMainHandler, mLayoutInflater, mFeatureFlags, coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController, mFalsingManager, new FalsingCollectorFake(), mNotificationLockscreenUserManager, mNotificationEntryManager, - mCommunalStateController, mKeyguardStateController, mStatusBarStateController, + mCommunalStateController, mKeyguardStateController, + mStatusBarStateController, + mStatusBarWindowStateController, mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper, mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor, mCommunalSourceMonitor, mMetricsLogger, mActivityManager, mConfigurationController, @@ -560,6 +569,12 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); } + @After + public void tearDown() { + mNotificationPanelViewController.cancelHeightAnimator(); + mMainHandler.removeCallbacksAndMessages(null); + } + @Test public void testSetPanelScrimMinFraction() { mNotificationPanelViewController.setPanelScrimMinFraction(0.5f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt new file mode 100644 index 000000000000..12e71af6997e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt @@ -0,0 +1,241 @@ +/* + * 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.statusbar.phone + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.MotionEvent +import androidx.test.filters.SmallTest +import com.android.keyguard.LockIconViewController +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.dock.DockManager +import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.NotificationShadeDepthController +import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.statusbar.window.StatusBarWindowStateController +import com.android.systemui.tuner.TunerService +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class NotificationShadeWindowViewControllerTest : SysuiTestCase() { + private lateinit var mController: NotificationShadeWindowViewController + + @Mock + private lateinit var mView: NotificationShadeWindowView + @Mock + private lateinit var mTunerService: TunerService + @Mock + private lateinit var mStatusBarStateController: SysuiStatusBarStateController + @Mock + private lateinit var mStatusBar: StatusBar + @Mock + private lateinit var mDockManager: DockManager + @Mock + private lateinit var mNotificationPanelViewController: NotificationPanelViewController + @Mock + private lateinit var mNotificationShadeDepthController: NotificationShadeDepthController + @Mock + private lateinit var mNotificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController + @Mock + private lateinit var mStatusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock + private lateinit var mStatusBarWindowStateController: StatusBarWindowStateController + @Mock + private lateinit var mLockscreenShadeTransitionController: LockscreenShadeTransitionController + @Mock + private lateinit var mLockIconViewController: LockIconViewController + @Mock + private lateinit var mPhoneStatusBarViewController: PhoneStatusBarViewController + + private lateinit var mInteractionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> + private lateinit var mInteractionEventHandler: InteractionEventHandler + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(mView.bottom).thenReturn(VIEW_BOTTOM) + + mController = NotificationShadeWindowViewController( + mLockscreenShadeTransitionController, + FalsingCollectorFake(), + mTunerService, + mStatusBarStateController, + mDockManager, + mNotificationShadeDepthController, + mView, + mNotificationPanelViewController, + PanelExpansionStateManager(), + stackScrollLayoutController, + mStatusBarKeyguardViewManager, + mStatusBarWindowStateController, + mLockIconViewController + ) + mController.setupExpandedStatusBar() + mController.setService(mStatusBar, mNotificationShadeWindowController) + + mInteractionEventHandlerCaptor = + ArgumentCaptor.forClass(InteractionEventHandler::class.java) + verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture()) + mInteractionEventHandler = mInteractionEventHandlerCaptor.value + } + + // Note: So far, these tests only cover interactions with the status bar view controller. More + // tests need to be added to test the rest of handleDispatchTouchEvent. + + @Test + fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() { + mController.setStatusBarViewController(null) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv) + + assertThat(returnVal).isFalse() + } + + @Test + fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() { + mController.setStatusBarViewController(mPhoneStatusBarViewController) + val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0) + whenever(mPhoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(ev) + + verify(mPhoneStatusBarViewController).sendTouchToView(ev) + assertThat(returnVal).isTrue() + } + + @Test + fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() { + mController.setStatusBarViewController(mPhoneStatusBarViewController) + val downEvBelow = MotionEvent.obtain( + 0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0 + ) + mInteractionEventHandler.handleDispatchTouchEvent(downEvBelow) + + val nextEvent = MotionEvent.obtain( + 0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0 + ) + whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent) + + verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent) + assertThat(returnVal).isTrue() + } + + @Test + fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() { + mController.setStatusBarViewController(mPhoneStatusBarViewController) + whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + whenever(mPhoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv) + + verify(mPhoneStatusBarViewController).sendTouchToView(downEv) + assertThat(returnVal).isTrue() + } + + @Test + fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() { + mController.setStatusBarViewController(mPhoneStatusBarViewController) + whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + // Item we're testing + whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(false) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv) + + verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv) + assertThat(returnVal).isNull() + } + + @Test + fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() { + mController.setStatusBarViewController(mPhoneStatusBarViewController) + whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true) + // Item we're testing + whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(false) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv) + + verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv) + assertThat(returnVal).isNull() + } + + @Test + fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() { + mController.setStatusBarViewController(mPhoneStatusBarViewController) + whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + // Item we're testing + whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(false) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv) + + verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv) + assertThat(returnVal).isTrue() + } + + @Test + fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() { + mController.setStatusBarViewController(mPhoneStatusBarViewController) + whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true) + whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true) + whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) + .thenReturn(true) + + // Down event first + mInteractionEventHandler.handleDispatchTouchEvent(downEv) + + // Then another event + val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true) + + val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent) + + verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent) + assertThat(returnVal).isTrue() + } +} + +private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) +private const val VIEW_BOTTOM = 100 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 1adba6e64c6d..d885da8b738c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -37,24 +37,15 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dock.DockManager; -import com.android.systemui.doze.DozeLog; -import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.tuner.TunerService; import org.junit.Before; @@ -73,29 +64,19 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { private NotificationShadeWindowView mView; private NotificationShadeWindowViewController mController; - @Mock private NotificationWakeUpCoordinator mCoordinator; - @Mock private PulseExpansionHandler mPulseExpansionHandler; - @Mock private DynamicPrivacyController mDynamicPrivacyController; - @Mock private KeyguardBypassController mBypassController; - @Mock private PluginManager mPluginManager; @Mock private TunerService mTunerService; @Mock private DragDownHelper mDragDownHelper; - @Mock private KeyguardStateController mKeyguardStateController; @Mock private SysuiStatusBarStateController mStatusBarStateController; @Mock private ShadeController mShadeController; - @Mock private NotificationLockscreenUserManager mNotificationLockScreenUserManager; - @Mock private NotificationEntryManager mNotificationEntryManager; @Mock private StatusBar mStatusBar; - @Mock private DozeLog mDozeLog; - @Mock private DozeParameters mDozeParameters; @Mock private DockManager mDockManager; @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; @Mock private NotificationShadeDepthController mNotificationShadeDepthController; - @Mock private StatusBarWindowController mStatusBarWindowController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + @Mock private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; @Mock private LockIconViewController mLockIconViewController; @@ -117,30 +98,18 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { when(mDockManager.isDocked()).thenReturn(false); mController = new NotificationShadeWindowViewController( - mCoordinator, - mPulseExpansionHandler, - mDynamicPrivacyController, - mBypassController, mLockscreenShadeTransitionController, new FalsingCollectorFake(), - mPluginManager, mTunerService, - mNotificationLockScreenUserManager, - mNotificationEntryManager, - mKeyguardStateController, mStatusBarStateController, - mDozeLog, - mDozeParameters, - new CommandQueue(mContext), - mShadeController, mDockManager, mNotificationShadeDepthController, mView, mNotificationPanelViewController, new PanelExpansionStateManager(), - mStatusBarWindowController, mNotificationStackScrollLayoutController, mStatusBarKeyguardViewManager, + mStatusBarWindowStateController, mLockIconViewController); mController.setupExpandedStatusBar(); mController.setService(mStatusBar, mNotificationShadeWindowController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 235de1e7cb7a..c65a6b6cde1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -77,9 +77,10 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { val parent = FrameLayout(mContext) // add parent to keep layout params view = LayoutInflater.from(mContext) .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView + view.setLeftTopRightBottom(VIEW_LEFT, VIEW_TOP, VIEW_RIGHT, VIEW_BOTTOM) } - controller = createController(view) + controller = createAndInitController(view) } @Test @@ -99,8 +100,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { val view = createViewMock() val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) unfoldConfig.isEnabled = true - controller = createController(view) - controller.init() + controller = createAndInitController(view) verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture()) argumentCaptor.value.onPreDraw() @@ -108,6 +108,64 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { verify(moveFromCenterAnimation).onViewsReady(any()) } + @Test + fun touchIsWithinView_inBounds_returnsTrue() { + val view = createViewMockWithScreenLocation() + controller = createAndInitController(view) + + assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_TOP + 1f)).isTrue() + } + + @Test + fun touchIsWithinView_onTopLeftCorner_returnsTrue() { + val view = createViewMockWithScreenLocation() + controller = createAndInitController(view) + + assertThat(controller.touchIsWithinView(VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())).isTrue() + } + + @Test + fun touchIsWithinView_onBottomRightCorner_returnsTrue() { + val view = createViewMockWithScreenLocation() + controller = createAndInitController(view) + + assertThat(controller.touchIsWithinView( + VIEW_RIGHT.toFloat(), VIEW_BOTTOM.toFloat()) + ).isTrue() + } + + @Test + fun touchIsWithinView_xTooSmall_returnsFalse() { + val view = createViewMockWithScreenLocation() + controller = createAndInitController(view) + + assertThat(controller.touchIsWithinView(VIEW_LEFT - 1f, VIEW_TOP + 1f)).isFalse() + } + + @Test + fun touchIsWithinView_xTooLarge_returnsFalse() { + val view = createViewMockWithScreenLocation() + controller = createAndInitController(view) + + assertThat(controller.touchIsWithinView(VIEW_RIGHT + 1f, VIEW_TOP + 1f)).isFalse() + } + + @Test + fun touchIsWithinView_yTooSmall_returnsFalse() { + val view = createViewMockWithScreenLocation() + controller = createAndInitController(view) + + assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_TOP - 1f)).isFalse() + } + + @Test + fun touchIsWithinView_yTooLarge_returnsFalse() { + val view = createViewMockWithScreenLocation() + controller = createAndInitController(view) + + assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse() + } + private fun createViewMock(): PhoneStatusBarView { val view = spy(view) val viewTreeObserver = mock(ViewTreeObserver::class.java) @@ -116,12 +174,23 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { return view } - private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController { + private fun createViewMockWithScreenLocation(): PhoneStatusBarView { + val view = spy(view) + val location = IntArray(2) + location[0] = VIEW_LEFT + location[1] = VIEW_TOP + `when`(view.locationOnScreen).thenReturn(location) + return view + } + + private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController { return PhoneStatusBarViewController.Factory( Optional.of(sysuiUnfoldComponent), Optional.of(progressProvider), configurationController - ).create(view, touchEventHandler) + ).create(view, touchEventHandler).also { + it.init() + } } private class UnfoldConfig : UnfoldTransitionConfig { @@ -142,3 +211,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } } } + +private const val VIEW_LEFT = 30 +private const val VIEW_RIGHT = 100 +private const val VIEW_TOP = 40 +private const val VIEW_BOTTOM = 100 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java index 0131293656e7..aabf9235c822 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java @@ -36,7 +36,6 @@ import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; -import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; @@ -46,8 +45,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import org.junit.Before; @@ -84,6 +81,7 @@ public class StatusBarCommandQueueCallbacksTest extends SysuiTestCase { @Mock private VibratorHelper mVibratorHelper; @Mock private Vibrator mVibrator; @Mock private LightBarController mLightBarController; + @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; StatusBarCommandQueueCallbacks mSbcqCallbacks; @@ -112,8 +110,7 @@ public class StatusBarCommandQueueCallbacksTest extends SysuiTestCase { mStatusBarStateController, mNotificationShadeWindowView, mNotificationStackScrollLayoutController, - new StatusBarHideIconsForBouncerManager( - mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()), + mStatusBarHideIconsForBouncerManager, mPowerManager, mVibratorHelper, Optional.of(mVibrator), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 07ec0e23aa01..743311f99ca2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -48,6 +48,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.widget.LockPatternUtils; @@ -64,7 +65,6 @@ import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotifPipelineFlags; -import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -143,6 +143,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { private StatusBarNotificationActivityStarter mNotificationActivityStarter; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; + @Mock + private InteractionJankMonitor mJankMonitor; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private NotificationTestHelper mNotificationTestHelper; @@ -197,7 +199,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { new NotificationLaunchAnimatorControllerProvider( mock(NotificationShadeWindowViewController.class), mock( NotificationListContainer.class), - headsUpManager); + headsUpManager, + mJankMonitor); mNotificationActivityStarter = new StatusBarNotificationActivityStarter.Builder( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 9d5b17ea6738..bb8bad39ab31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -44,6 +44,7 @@ import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.IntentFilter; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; @@ -69,6 +70,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.internal.statusbar.IStatusBarService; @@ -147,6 +149,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.MessageRouterImpl; @@ -160,6 +163,7 @@ import com.android.wm.shell.startingsurface.StartingSurface; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -174,6 +178,9 @@ import dagger.Lazy; @RunWithLooper(setAsMainLooper = true) public class StatusBarTest extends SysuiTestCase { + private static final int FOLD_STATE_FOLDED = 0; + private static final int FOLD_STATE_UNFOLDED = 1; + private StatusBar mStatusBar; private FakeMetricsLogger mMetricsLogger; private PowerManager mPowerManager; @@ -228,6 +235,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private DynamicPrivacyController mDynamicPrivacyController; @Mock private AutoHideController mAutoHideController; @Mock private StatusBarWindowController mStatusBarWindowController; + @Mock private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager; @Mock private UserSwitcherController mUserSwitcherController; @Mock private NetworkController mNetworkController; @@ -263,6 +271,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory; @Mock private WallpaperController mWallpaperController; @Mock private OngoingCallController mOngoingCallController; + @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private LockscreenShadeTransitionController mLockscreenTransitionController; @Mock private FeatureFlags mFeatureFlags; @Mock private NotificationVisibilityProvider mVisibilityProvider; @@ -276,6 +285,8 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock private NotifPipelineFlags mNotifPipelineFlags; @Mock private NotifLiveDataStore mNotifLiveDataStore; + @Mock private InteractionJankMonitor mJankMonitor; + @Mock private DeviceStateManager mDeviceStateManager; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -374,6 +385,7 @@ public class StatusBarTest extends SysuiTestCase { mLightBarController, mAutoHideController, mStatusBarWindowController, + mStatusBarWindowStateController, mKeyguardUpdateMonitor, mStatusBarSignalPolicy, mPulseExpansionHandler, @@ -449,7 +461,7 @@ public class StatusBarTest extends SysuiTestCase { mScreenOffAnimationController, mWallpaperController, mOngoingCallController, - new StatusBarHideIconsForBouncerManager(mCommandQueue, mMainExecutor, mDumpManager), + mStatusBarHideIconsForBouncerManager, mLockscreenTransitionController, mFeatureFlags, mKeyguardUnlockAnimationController, @@ -459,7 +471,9 @@ public class StatusBarTest extends SysuiTestCase { mWallpaperManager, Optional.of(mStartingSurface), mActivityLaunchAnimator, - mNotifPipelineFlags); + mNotifPipelineFlags, + mJankMonitor, + mDeviceStateManager); when(mKeyguardViewMediator.registerStatusBar( any(StatusBar.class), any(NotificationPanelViewController.class), @@ -942,6 +956,47 @@ public class StatusBarTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).updateResources(); } + @Test + public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() { + setFoldedStates(FOLD_STATE_FOLDED); + setGoToSleepStates(FOLD_STATE_FOLDED); + when(mNotificationPanelViewController.isFullyExpanded()).thenReturn(true); + + setDeviceState(FOLD_STATE_UNFOLDED); + + verify(mStatusBarStateController).setLeaveOpenOnKeyguardHide(true); + } + + @Test + public void deviceStateChange_unfolded_shadeClose_doesNotSetLeaveOpenOnKeyguardHide() { + setFoldedStates(FOLD_STATE_FOLDED); + setGoToSleepStates(FOLD_STATE_FOLDED); + when(mNotificationPanelViewController.isFullyExpanded()).thenReturn(false); + + setDeviceState(FOLD_STATE_UNFOLDED); + + verify(mStatusBarStateController, never()).setLeaveOpenOnKeyguardHide(true); + } + + private void setDeviceState(int state) { + ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor = + ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); + verify(mDeviceStateManager).registerCallback(any(), callbackCaptor.capture()); + callbackCaptor.getValue().onStateChanged(state); + } + + private void setGoToSleepStates(int... states) { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.array.config_deviceStatesOnWhichToSleep, + states); + } + + private void setFoldedStates(int... states) { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.array.config_foldedDeviceStates, + states); + } + public static class TestableNotificationInterruptStateProviderImpl extends NotificationInterruptStateProviderImpl { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index b97f053b24b5..a630840fab0e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -37,7 +37,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; -import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogcatEchoTracker; @@ -57,8 +56,6 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -96,6 +93,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private HeadsUpAppearanceController mHeadsUpAppearanceController; @Mock private NotificationPanelViewController mNotificationPanelViewController; + @Mock + private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; public CollapsedStatusBarFragmentTest() { super(CollapsedStatusBarFragment.class); @@ -325,8 +324,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { new PanelExpansionStateManager(), mock(FeatureFlags.class), mStatusBarIconController, - new StatusBarHideIconsForBouncerManager( - mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()), + mStatusBarHideIconsForBouncerManager, mKeyguardStateController, mNotificationPanelViewController, mNetworkController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index 30717f431f5b..db7b2f20fa4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -225,6 +225,11 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Override + public boolean isCameraRotationEnabled() { + throw new AssertionError("Not implemented"); + } + + @Override public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener, int userHandle) { throw new AssertionError("Not implemented"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java index 5e852e303533..d15ba2615b8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,9 +78,15 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { mHeadsUpManager = new TestableHeadsUpManager(mContext); super.setUp(); + mHeadsUpManager.mHandler.removeCallbacksAndMessages(null); mHeadsUpManager.mHandler = mTestHandler; } + @After + public void tearDown() { + mTestHandler.removeCallbacksAndMessages(null); + } + @Test public void testShowNotification_autoDismissesWithAccessibilityTimeout() { doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt new file mode 100644 index 000000000000..8576d4f56906 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt @@ -0,0 +1,102 @@ +/* + * 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.statusbar.window + +import android.app.StatusBarManager.WindowVisibleState +import android.app.StatusBarManager.WINDOW_NAVIGATION_BAR +import android.app.StatusBarManager.WINDOW_STATE_HIDDEN +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.CommandQueue +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +class StatusBarWindowStateControllerTest : SysuiTestCase() { + private lateinit var controller: StatusBarWindowStateController + private lateinit var callback: CommandQueue.Callbacks + + @Mock + private lateinit var commandQueue: CommandQueue + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + controller = StatusBarWindowStateController(DISPLAY_ID, commandQueue) + + val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + verify(commandQueue).addCallback(callbackCaptor.capture()) + callback = callbackCaptor.value!! + } + + @Test + fun setWindowState_notSameDisplayId_listenersNotNotified() { + val listener = TestListener() + controller.addListener(listener) + + callback.setWindowState(DISPLAY_ID + 1, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN) + + assertThat(listener.state).isNull() + } + + @Test + fun setWindowState_notStatusBarWindow_listenersNotNotified() { + val listener = TestListener() + controller.addListener(listener) + + callback.setWindowState(DISPLAY_ID, WINDOW_NAVIGATION_BAR, WINDOW_STATE_HIDDEN) + + assertThat(listener.state).isNull() + } + + @Test + fun setWindowState_sameState_listenersNotNotified() { + val listener = TestListener() + controller.addListener(listener) + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + + assertThat(listener.state).isNull() + } + + @Test + fun setWindowState_newState_listenersNotified() { + val listener = TestListener() + controller.addListener(listener) + val newState = WINDOW_STATE_HIDDEN + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, newState) + + assertThat(listener.state).isEqualTo(newState) + } + + private class TestListener : StatusBarWindowStateListener { + @WindowVisibleState var state: Int? = null + override fun onStatusBarWindowStateChanged(@WindowVisibleState state: Int) { + this.state = state + } + } +} + +private const val DISPLAY_ID = 10 diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java index be110242a3eb..4f9cb35db1a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java @@ -51,6 +51,11 @@ public class FakeRotationLockController extends BaseLeakChecker<RotationLockCont } @Override + public boolean isCameraRotationEnabled() { + return false; + } + + @Override public void setRotationLockedAtAngle(boolean locked, int rotation) { } diff --git a/services/Android.bp b/services/Android.bp index c830c226cb66..74d7f654df16 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -224,19 +224,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index f050b6622a5d..ad3e1d56c51b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -2033,6 +2033,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override + public void setCacheEnabled(boolean enabled) { + if (svcConnTracingEnabled()) { + logTraceSvcConn("setCacheEnabled", "enabled=" + enabled); + } + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + mUsesAccessibilityCache = enabled; + mSystemSupport.onClientChangeLocked(true); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void logTrace(long timestamp, String where, long loggingTypes, String callingParams, int processId, long threadId, int callingUid, Bundle callingStack) { if (mTrace.isA11yTracingEnabledForTypes(loggingTypes)) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 52a6dc143df7..e59a3d633cfd 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -193,6 +193,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private static final int OWN_PROCESS_ID = android.os.Process.myPid(); + public static final int INVALID_SERVICE_ID = -1; + // Each service has an ID. Also provide one for magnification gesture handling public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index 38615feeba48..72bc8503b7a5 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -19,6 +19,8 @@ package com.android.server.accessibility.magnification; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; +import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID; + import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.NonNull; @@ -117,8 +119,7 @@ public class FullScreenMagnificationController implements private final int mDisplayId; - private static final int INVALID_ID = -1; - private int mIdOfLastServiceToMagnify = INVALID_ID; + private int mIdOfLastServiceToMagnify = INVALID_SERVICE_ID; private boolean mMagnificationActivated = false; DisplayMagnification(int displayId) { @@ -425,7 +426,7 @@ public class FullScreenMagnificationController implements } final float scale = getScale(); - offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_ID); + offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_SERVICE_ID); } } @@ -472,7 +473,7 @@ public class FullScreenMagnificationController implements spec.clear(); onMagnificationChangedLocked(); } - mIdOfLastServiceToMagnify = INVALID_ID; + mIdOfLastServiceToMagnify = INVALID_SERVICE_ID; mForceShowMagnifiableBounds = false; sendSpecToAnimation(spec, animationCallback); return changed; @@ -519,7 +520,7 @@ public class FullScreenMagnificationController implements } final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback); - if (isMagnifying() && (id != INVALID_ID)) { + if (isMagnifying() && (id != INVALID_SERVICE_ID)) { mIdOfLastServiceToMagnify = id; mMagnificationInfoChangedCallback.onRequestMagnificationSpec(mDisplayId, mIdOfLastServiceToMagnify); @@ -583,7 +584,7 @@ public class FullScreenMagnificationController implements if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { onMagnificationChangedLocked(); } - if (id != INVALID_ID) { + if (id != INVALID_SERVICE_ID) { mIdOfLastServiceToMagnify = id; } sendSpecToAnimation(mCurrentMagnificationSpec, null); 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 791da694c811..037dc1f6795c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -22,6 +22,8 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; +import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; + import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.annotation.Nullable; @@ -240,9 +242,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb * @param config The targeting magnification config * @param animate {@code true} to animate the transition, {@code false} * to transition immediately + * @param id The ID of the service requesting the change */ public void transitionMagnificationConfigMode(int displayId, MagnificationConfig config, - boolean animate) { + boolean animate, int id) { synchronized (mLock) { final int targetMode = config.getMode(); final PointF currentBoundsCenter = getCurrentMagnificationBoundsCenterLocked(displayId, @@ -273,7 +276,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb screenMagnificationController.reset(displayId, false); windowMagnificationMgr.enableWindowMagnification(displayId, scale, magnificationCenter.x, magnificationCenter.y, - animate ? STUB_ANIMATION_CALLBACK : null); + animate ? STUB_ANIMATION_CALLBACK : null, id); } else if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { windowMagnificationMgr.disableWindowMagnification(displayId, false, null); if (!screenMagnificationController.isRegistered(displayId)) { @@ -281,7 +284,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb } screenMagnificationController.setScaleAndCenter(displayId, scale, magnificationCenter.x, magnificationCenter.y, animate, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + id); } } } @@ -337,7 +340,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb public void onRequestMagnificationSpec(int displayId, int serviceId) { final WindowMagnificationManager windowMagnificationManager; synchronized (mLock) { - if (serviceId == AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID) { + if (serviceId == MAGNIFICATION_GESTURE_HANDLER_ID) { return; } updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); @@ -575,7 +578,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { if (mWindowMagnificationMgr == null) { mWindowMagnificationMgr = new WindowMagnificationManager(mContext, - mUserId, this, mAms.getTraceManager(), + mLock, this, mAms.getTraceManager(), mScaleProvider); } return mWindowMagnificationMgr; @@ -718,11 +721,12 @@ public class MagnificationController implements WindowMagnificationManager.Callb } fullScreenMagnificationController.setScaleAndCenter(mDisplayId, mCurrentScale, mCurrentCenter.x, mCurrentCenter.y, mAnimate, - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + MAGNIFICATION_GESTURE_HANDLER_ID); } else { getWindowMagnificationMgr().enableWindowMagnification(mDisplayId, mCurrentScale, mCurrentCenter.x, - mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null); + mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null, + MAGNIFICATION_GESTURE_HANDLER_ID); } } } 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 7a525ee23d27..40f77b04d5de 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java @@ -99,7 +99,7 @@ public class MagnificationProcessor { */ public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, boolean animate, int id) { - if (transitionModeIfNeeded(displayId, config, animate)) { + if (transitionModeIfNeeded(displayId, config, animate, id)) { return true; } @@ -114,7 +114,8 @@ public class MagnificationProcessor { } else if (configMode == MAGNIFICATION_MODE_WINDOW) { return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId, config.getScale(), config.getCenterX(), config.getCenterY(), - animate ? STUB_ANIMATION_CALLBACK : null); + animate ? STUB_ANIMATION_CALLBACK : null, + id); } return false; } @@ -136,13 +137,13 @@ public class MagnificationProcessor { * mode when the controlling mode is unchanged or the controlling magnifier is not activated. */ private boolean transitionModeIfNeeded(int displayId, MagnificationConfig config, - boolean animate) { + boolean animate, int id) { int currentMode = getControllingMode(displayId); if (currentMode == config.getMode() || !mController.hasDisableMagnificationCallback(displayId)) { return false; } - mController.transitionMagnificationConfigMode(displayId, config, animate); + mController.transitionMagnificationConfigMode(displayId, config, animate, id); return true; } @@ -237,7 +238,8 @@ public class MagnificationProcessor { if (mode == MAGNIFICATION_MODE_FULLSCREEN) { return mController.getFullScreenMagnificationController().reset(displayId, animate); } else if (mode == MAGNIFICATION_MODE_WINDOW) { - return mController.getWindowMagnificationMgr().reset(displayId); + return mController.getWindowMagnificationMgr().disableWindowMagnification(displayId, + false, animate ? STUB_ANIMATION_CALLBACK : null); } return false; } @@ -256,11 +258,15 @@ public class MagnificationProcessor { } /** - * {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)} + * Resets all the magnifiers on all the displays. + * Called when the a11y service connection that has changed the current magnification spec is + * unbound or the binder died. + * + * @param connectionId The connection id */ - // TODO: support window magnification public void resetAllIfNeeded(int connectionId) { mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId); + mController.getWindowMagnificationMgr().resetAllIfNeeded(connectionId); } /** 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 3f6ff25dafe5..c4a577d6e461 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -20,6 +20,9 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNI import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; +import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID; +import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -84,7 +87,7 @@ public class WindowMagnificationManager implements }) public @interface WindowPosition {} - private final Object mLock = new Object(); + private final Object mLock; private final Context mContext; @VisibleForTesting @GuardedBy("mLock") @@ -149,9 +152,10 @@ public class WindowMagnificationManager implements private final AccessibilityTraceManager mTrace; private final MagnificationScaleProvider mScaleProvider; - public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback, + public WindowMagnificationManager(Context context, Object lock, @NonNull Callback callback, AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider) { mContext = context; + mLock = lock; mCallback = callback; mTrace = trace; mScaleProvider = scaleProvider; @@ -253,7 +257,26 @@ public class WindowMagnificationManager implements } mWindowMagnifiers.clear(); } + } + /** + * Resets the window magnifier on all displays that had been controlled by the + * specified service connection. Called when the service connection is unbound + * or binder died. + * + * @param connectionId The connection id + */ + public void resetAllIfNeeded(int connectionId) { + synchronized (mLock) { + for (int i = 0; i < mWindowMagnifiers.size(); i++) { + final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i); + if (magnifier != null + && magnifier.mEnabled + && connectionId == magnifier.getIdOfLastServiceToControl()) { + magnifier.disableWindowMagnificationInternal(null); + } + } + } } private void resetWindowMagnifiers() { @@ -310,7 +333,7 @@ public class WindowMagnificationManager implements public boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY) { return enableWindowMagnification(displayId, scale, centerX, centerY, - STUB_ANIMATION_CALLBACK); + STUB_ANIMATION_CALLBACK, MAGNIFICATION_GESTURE_HANDLER_ID); } /** @@ -324,12 +347,13 @@ public class WindowMagnificationManager implements * @param centerY The screen-relative Y coordinate around which to center for magnification, * or {@link Float#NaN} to leave unchanged. * @param animationCallback Called when the animation result is valid. + * @param id The connection ID * @return {@code true} if the magnification is enabled successfully. */ public boolean enableWindowMagnification(int displayId, float scale, float centerX, - float centerY, @Nullable MagnificationAnimationCallback animationCallback) { + float centerY, @Nullable MagnificationAnimationCallback animationCallback, int id) { return enableWindowMagnification(displayId, scale, centerX, centerY, animationCallback, - WINDOW_POSITION_AT_CENTER); + WINDOW_POSITION_AT_CENTER, id); } /** @@ -348,7 +372,7 @@ public class WindowMagnificationManager implements public boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @WindowPosition int windowPosition) { return enableWindowMagnification(displayId, scale, centerX, centerY, - STUB_ANIMATION_CALLBACK, windowPosition); + STUB_ANIMATION_CALLBACK, windowPosition, MAGNIFICATION_GESTURE_HANDLER_ID); } /** @@ -367,7 +391,7 @@ public class WindowMagnificationManager implements */ public boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback, - @WindowPosition int windowPosition) { + @WindowPosition int windowPosition, int id) { final boolean enabled; boolean previousEnabled; synchronized (mLock) { @@ -380,7 +404,7 @@ public class WindowMagnificationManager implements } previousEnabled = magnifier.mEnabled; enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, - animationCallback, windowPosition); + animationCallback, windowPosition, id); } if (enabled && !previousEnabled) { @@ -394,9 +418,10 @@ public class WindowMagnificationManager implements * * @param displayId The logical display id. * @param clear {@true} Clears the state of window magnification. + * @return {@code true} if the magnification is turned to be disabled successfully */ - void disableWindowMagnification(int displayId, boolean clear) { - disableWindowMagnification(displayId, clear, STUB_ANIMATION_CALLBACK); + boolean disableWindowMagnification(int displayId, boolean clear) { + return disableWindowMagnification(displayId, clear, STUB_ANIMATION_CALLBACK); } /** @@ -405,14 +430,15 @@ public class WindowMagnificationManager implements * @param displayId The logical display id. * @param clear {@true} Clears the state of window magnification. * @param animationCallback Called when the animation result is valid. + * @return {@code true} if the magnification is turned to be disabled successfully */ - void disableWindowMagnification(int displayId, boolean clear, + public boolean disableWindowMagnification(int displayId, boolean clear, MagnificationAnimationCallback animationCallback) { final boolean disabled; synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null || mConnectionWrapper == null) { - return; + return false; } disabled = magnifier.disableWindowMagnificationInternal(animationCallback); if (clear) { @@ -423,6 +449,7 @@ public class WindowMagnificationManager implements if (disabled) { mCallback.onWindowMagnificationActivationState(displayId, false); } + return disabled; } /** @@ -490,7 +517,7 @@ public class WindowMagnificationManager implements public float getScale(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); - if (magnifier == null) { + if (magnifier == null || !magnifier.mEnabled) { return 1.0f; } return magnifier.getScale(); @@ -548,7 +575,7 @@ public class WindowMagnificationManager implements public float getCenterX(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); - if (magnifier == null) { + if (magnifier == null || !magnifier.mEnabled) { return Float.NaN; } return magnifier.getCenterX(); @@ -564,7 +591,7 @@ public class WindowMagnificationManager implements public float getCenterY(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); - if (magnifier == null) { + if (magnifier == null || !magnifier.mEnabled) { return Float.NaN; } return magnifier.getCenterY(); @@ -581,7 +608,7 @@ public class WindowMagnificationManager implements public void getMagnificationSourceBounds(int displayId, @NonNull Region outRegion) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); - if (magnifier == null) { + if (magnifier == null || !magnifier.mEnabled) { outRegion.setEmpty(); } else { outRegion.set(magnifier.mSourceBounds); @@ -590,24 +617,6 @@ public class WindowMagnificationManager implements } /** - * 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. @@ -722,6 +731,9 @@ public class WindowMagnificationManager implements /** * A class manipulates window magnification per display and contains the magnification * information. + * <p> + * This class requires to hold the lock when controlling the magnifier. + * </p> */ private static class WindowMagnifier { @@ -735,6 +747,8 @@ public class WindowMagnificationManager implements // The magnified bounds on the screen. private final Rect mSourceBounds = new Rect(); + private int mIdOfLastServiceToControl = INVALID_SERVICE_ID; + private PointF mMagnificationFrameOffsetRatio = new PointF(0f, 0f); WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) { @@ -745,7 +759,7 @@ public class WindowMagnificationManager implements @GuardedBy("mLock") boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback, - @WindowPosition int windowPosition) { + @WindowPosition int windowPosition, int id) { // Handle defaults. The scale may be NAN when just updating magnification center. if (Float.isNaN(scale)) { scale = getScale(); @@ -757,7 +771,7 @@ public class WindowMagnificationManager implements mMagnificationFrameOffsetRatio.y, animationCallback)) { mScale = normScale; mEnabled = true; - + mIdOfLastServiceToControl = id; return true; } return false; @@ -785,7 +799,7 @@ public class WindowMagnificationManager implements if (mWindowMagnificationManager.disableWindowMagnificationInternal( mDisplayId, animationResultCallback)) { mEnabled = false; - + mIdOfLastServiceToControl = INVALID_SERVICE_ID; return true; } return false; @@ -813,6 +827,13 @@ public class WindowMagnificationManager implements mBounds.set(rect); } + /** + * Returns the ID of the last service that changed the magnification config. + */ + int getIdOfLastServiceToControl() { + return mIdOfLastServiceToControl; + } + @GuardedBy("mLock") int pointersInWindow(MotionEvent motionEvent) { int count = 0; @@ -840,6 +861,8 @@ public class WindowMagnificationManager implements @GuardedBy("mLock") void reset() { mEnabled = false; + mIdOfLastServiceToControl = INVALID_SERVICE_ID; + mSourceBounds.setEmpty(); } @GuardedBy("mLock") @@ -849,12 +872,12 @@ public class WindowMagnificationManager implements @GuardedBy("mLock") float getCenterX() { - return mEnabled ? mSourceBounds.exactCenterX() : Float.NaN; + return mSourceBounds.exactCenterX(); } @GuardedBy("mLock") float getCenterY() { - return mEnabled ? mSourceBounds.exactCenterY() : Float.NaN; + return mSourceBounds.exactCenterY(); } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 422749e9a75a..051281c5dd08 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -241,9 +241,6 @@ public final class AutofillManagerService protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver, @NonNull ContentObserver observer) { resolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, observer, - UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.AUTOFILL_LOGGING_LEVEL), false, observer, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.Global.getUriFor( @@ -274,8 +271,6 @@ public final class AutofillManagerService break; default: Slog.w(TAG, "Unexpected property (" + property + "); updating cache instead"); - // fall through - case Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES: synchronized (mLock) { updateCachedServiceLocked(userId); } @@ -307,6 +302,9 @@ public final class AutofillManagerService case AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT: setDeviceConfigProperties(); break; + case AutofillManager.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES: + updateCachedServices(); + break; default: Slog.i(mTag, "Ignoring change on " + key); } @@ -588,6 +586,15 @@ public final class AutofillManagerService } } + private void updateCachedServices() { + List<UserInfo> supportedUsers = getSupportedUsers(); + for (UserInfo userInfo : supportedUsers) { + synchronized (mLock) { + updateCachedServiceLocked(userInfo.id); + } + } + } + // Called by Shell command. void calculateScore(@Nullable String algorithmName, @NonNull String value1, @NonNull String value2, @NonNull RemoteCallback callback) { @@ -702,31 +709,44 @@ public final class AutofillManagerService return; } - final Map<String, String[]> whiteListedPackages = getWhitelistedCompatModePackages(); + final Map<String, String[]> allowedPackages = getAllowedCompatModePackages(); final int compatPackageCount = compatPackages.size(); for (int i = 0; i < compatPackageCount; i++) { final String packageName = compatPackages.keyAt(i); - if (whiteListedPackages == null || !whiteListedPackages.containsKey(packageName)) { - Slog.w(TAG, "Ignoring not whitelisted compat package " + packageName); + if (allowedPackages == null || !allowedPackages.containsKey(packageName)) { + Slog.w(TAG, "Ignoring not allowed compat package " + packageName); continue; } final Long maxVersionCode = compatPackages.valueAt(i); if (maxVersionCode != null) { mAutofillCompatState.addCompatibilityModeRequest(packageName, - maxVersionCode, whiteListedPackages.get(packageName), userId); + maxVersionCode, allowedPackages.get(packageName), userId); } } } - private String getWhitelistedCompatModePackagesFromSettings() { + private String getAllowedCompatModePackagesFromDeviceConfig() { + String config = DeviceConfig.getString( + DeviceConfig.NAMESPACE_AUTOFILL, + AutofillManager.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES, + /* defaultValue */ null); + if (!TextUtils.isEmpty(config)) { + return config; + } + // Fallback to Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES if + // the device config is null. + return getAllowedCompatModePackagesFromSettings(); + } + + private String getAllowedCompatModePackagesFromSettings() { return Settings.Global.getString( getContext().getContentResolver(), Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES); } @Nullable - private Map<String, String[]> getWhitelistedCompatModePackages() { - return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings()); + private Map<String, String[]> getAllowedCompatModePackages() { + return getAllowedCompatModePackages(getAllowedCompatModePackagesFromDeviceConfig()); } private void send(@NonNull IResultReceiver receiver, int value) { @@ -771,7 +791,7 @@ public final class AutofillManagerService @Nullable @VisibleForTesting - static Map<String, String[]> getWhitelistedCompatModePackages(String setting) { + static Map<String, String[]> getAllowedCompatModePackages(String setting) { if (TextUtils.isEmpty(setting)) { return null; } @@ -1756,8 +1776,8 @@ public final class AutofillManagerService mUi.dump(pw); pw.print("Autofill Compat State: "); mAutofillCompatState.dump(prefix, pw); - pw.print("from settings: "); - pw.println(getWhitelistedCompatModePackagesFromSettings()); + pw.print("from device config: "); + pw.println(getAllowedCompatModePackagesFromDeviceConfig()); if (mSupportedSmartSuggestionModes != 0) { pw.print("Smart Suggestion modes: "); pw.println(getSmartSuggestionModeToString(mSupportedSmartSuggestionModes)); diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index f8da03539ae6..efa026baefac 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -3907,14 +3907,20 @@ public class UserBackupManagerService { } int operationType; + TransportConnection transportConnection = null; try { - operationType = getOperationTypeFromTransport( - mTransportManager.getTransportClientOrThrow(transport, /* caller */ - "BMS.beginRestoreSession")); + transportConnection = mTransportManager.getTransportClientOrThrow( + transport, /* caller */"BMS.beginRestoreSession"); + operationType = getOperationTypeFromTransport(transportConnection); } catch (TransportNotAvailableException | TransportNotRegisteredException | RemoteException e) { Slog.w(TAG, "Failed to get operation type from transport: " + e); return null; + } finally { + if (transportConnection != null) { + mTransportManager.disposeOfTransportClient(transportConnection, + /* caller */"BMS.beginRestoreSession"); + } } synchronized (this) { diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 637994f6d73d..1914164f195c 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -22,7 +22,6 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME; import static android.content.ComponentName.createRelative; -import static com.android.internal.util.CollectionUtils.filter; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation; @@ -57,6 +56,7 @@ import com.android.internal.util.ArrayUtils; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -124,14 +124,17 @@ class AssociationRequestsProcessor { private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5; private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min; - private final Context mContext; - private final CompanionDeviceManagerService mService; - private final PackageManagerInternal mPackageManager; + private final @NonNull Context mContext; + private final @NonNull CompanionDeviceManagerService mService; + private final @NonNull PackageManagerInternal mPackageManager; + private final @NonNull AssociationStore mAssociationStore; - AssociationRequestsProcessor(CompanionDeviceManagerService service) { + AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service, + @NonNull AssociationStore associationStore) { mContext = service.getContext(); mService = service; mPackageManager = service.mPackageManagerInternal; + mAssociationStore = associationStore; } /** @@ -330,18 +333,24 @@ class AssociationRequestsProcessor { } // Throttle frequent associations - long now = System.currentTimeMillis(); - Set<AssociationInfo> recentAssociations = filter( - mService.getAssociations(userId, packageName), - a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS); - - if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) { - Slog.w(TAG, "Too many associations. " + packageName - + " already associated " + recentAssociations.size() - + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS - + "ms: " + recentAssociations); - return false; + final long now = System.currentTimeMillis(); + final List<AssociationInfo> associationForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + // Number of "recent" associations. + int recent = 0; + for (AssociationInfo association : associationForPackage) { + final boolean isRecent = + now - association.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS; + if (isRecent) { + if (++recent >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) { + Slog.w(TAG, "Too many associations: " + packageName + " already " + + "associated " + recent + " devices within the last " + + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS + "ms"); + return false; + } + } } + String[] sameOemCerts = mContext.getResources() .getStringArray(com.android.internal.R.array.config_companionDeviceCerts); diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java new file mode 100644 index 000000000000..58fc8f7fe5b6 --- /dev/null +++ b/services/companion/java/com/android/server/companion/AssociationStore.java @@ -0,0 +1,126 @@ +/* + * 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.server.companion; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.companion.AssociationInfo; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.List; + +/** + * Interface for a store of {@link AssociationInfo}-s. + */ +public interface AssociationStore { + + @IntDef(prefix = { "CHANGE_TYPE_" }, value = { + CHANGE_TYPE_ADDED, + CHANGE_TYPE_REMOVED, + CHANGE_TYPE_UPDATED_ADDRESS_CHANGED, + CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ChangeType {} + + int CHANGE_TYPE_ADDED = 0; + int CHANGE_TYPE_REMOVED = 1; + int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2; + int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3; + + /** Listener for any changes to {@link AssociationInfo}-s. */ + interface OnChangeListener { + default void onAssociationChanged( + @ChangeType int changeType, AssociationInfo association) {} + + default void onAssociationAdded(AssociationInfo association) {} + + default void onAssociationRemoved(AssociationInfo association) {} + + default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {} + } + + /** + * @return all CDM associations. + */ + @NonNull + Collection<AssociationInfo> getAssociations(); + + /** + * @return a {@link List} of associations that belong to the user. + */ + @NonNull + List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId); + + /** + * @return a {@link List} of association that belong to the package. + */ + @NonNull + List<AssociationInfo> getAssociationsForPackage( + @UserIdInt int userId, @NonNull String packageName); + + /** + * @return an association with the given address that belong to the given package if such an + * association exists, otherwise {@code null}. + */ + @Nullable + AssociationInfo getAssociationsForPackageWithAddress( + @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress); + + /** + * @return an association with the given id if such an association exists, otherwise + * {@code null}. + */ + @Nullable + AssociationInfo getAssociationById(int id); + + /** + * @return all associations with the given MAc address. + */ + @NonNull + List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress); + + /** Register a {@link OnChangeListener} */ + void registerListener(@NonNull OnChangeListener listener); + + /** Un-register a previously registered {@link OnChangeListener} */ + void unregisterListener(@NonNull OnChangeListener listener); + + /** @hide */ + static String changeTypeToString(@ChangeType int changeType) { + switch (changeType) { + case CHANGE_TYPE_ADDED: + return "ASSOCIATION_ADDED"; + + case CHANGE_TYPE_REMOVED: + return "ASSOCIATION_REMOVED"; + + case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: + return "ASSOCIATION_UPDATED"; + + case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: + return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED"; + + default: + return "Unknown (" + changeType + ")"; + } + } +} diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java new file mode 100644 index 000000000000..3f0200ea584f --- /dev/null +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -0,0 +1,302 @@ +/* + * 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.server.companion; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.companion.AssociationInfo; +import android.net.MacAddress; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Implementation of the {@link AssociationStore}, with addition of the methods for modification. + * <ul> + * <li> {@link #addAssociation(AssociationInfo)} + * <li> {@link #removeAssociation(int)} + * <li> {@link #updateAssociation(AssociationInfo)} + * </ul> + * + * The class has package-private access level, and instances of the class should only be created by + * the {@link CompanionDeviceManagerService}. + * Other system component (both inside and outside if the com.android.server.companion package) + * should use public {@link AssociationStore} interface. + */ +class AssociationStoreImpl implements AssociationStore { + private static final boolean DEBUG = false; + private static final String TAG = "AssociationStore"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final Map<Integer, AssociationInfo> mIdMap; + @GuardedBy("mLock") + private final Map<MacAddress, Set<Integer>> mAddressMap; + @GuardedBy("mLock") + private final SparseArray<List<AssociationInfo>> mCachedPerUser = new SparseArray<>(); + + @GuardedBy("mListeners") + private final Set<OnChangeListener> mListeners = new LinkedHashSet<>(); + + AssociationStoreImpl(Collection<AssociationInfo> associations) { + synchronized (mLock) { + final int size = associations.size(); + mIdMap = new HashMap<>(size); + mAddressMap = new HashMap<>(size); + + for (AssociationInfo association : associations) { + final int id = association.getId(); + mIdMap.put(id, association); + + final MacAddress address = association.getDeviceMacAddress(); + if (address != null) { + mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); + } + } + } + } + + void addAssociation(@NonNull AssociationInfo association) { + final int id = association.getId(); + + if (DEBUG) { + Log.i(TAG, "addAssociation() " + association.toShortString()); + Log.d(TAG, " association=" + association); + } + + synchronized (mLock) { + if (mIdMap.containsKey(id)) { + if (DEBUG) Log.w(TAG, "Association already stored."); + return; + } + mIdMap.put(id, association); + + final MacAddress address = association.getDeviceMacAddress(); + if (address != null) { + mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); + } + + invalidateCacheForUserLocked(association.getUserId()); + } + + broadcastChange(CHANGE_TYPE_ADDED, association); + } + + void updateAssociation(@NonNull AssociationInfo updated) { + final int id = updated.getId(); + + if (DEBUG) { + Log.i(TAG, "updateAssociation() " + updated.toShortString()); + Log.d(TAG, " updated=" + updated); + } + + final AssociationInfo current; + final boolean macAddressChanged; + synchronized (mLock) { + current = mIdMap.get(id); + if (current == null) { + if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist."); + return; + } + if (DEBUG) Log.d(TAG, " current=" + current); + + if (current.equals(updated)) { + if (DEBUG) Log.w(TAG, " No changes."); + return; + } + + // Update the ID-to-Association map. + mIdMap.put(id, updated); + + // Update the MacAddress-to-List<Association> map if needed. + final MacAddress updatedAddress = updated.getDeviceMacAddress(); + final MacAddress currentAddress = current.getDeviceMacAddress(); + macAddressChanged = Objects.equals( + current.getDeviceMacAddress(), updated.getDeviceMacAddress()); + if (macAddressChanged) { + if (currentAddress != null) { + mAddressMap.get(currentAddress).remove(id); + } + if (updatedAddress != null) { + mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id); + } + } + } + + final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED + : CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; + broadcastChange(changeType, updated); + } + + void removeAssociation(int id) { + if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id); + + final AssociationInfo association; + synchronized (mLock) { + association = mIdMap.remove(id); + + if (association == null) { + if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored."); + return; + } else { + if (DEBUG) { + Log.i(TAG, "removed " + association.toShortString()); + Log.d(TAG, " association=" + association); + } + } + + final MacAddress macAddress = association.getDeviceMacAddress(); + if (macAddress != null) { + mAddressMap.get(macAddress).remove(id); + } + + invalidateCacheForUserLocked(association.getUserId()); + } + + broadcastChange(CHANGE_TYPE_REMOVED, association); + } + + public @NonNull Collection<AssociationInfo> getAssociations() { + final Collection<AssociationInfo> allAssociations; + synchronized (mLock) { + allAssociations = mIdMap.values(); + } + return Collections.unmodifiableCollection(allAssociations); + } + + public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) { + synchronized (mLock) { + return getAssociationsForUserLocked(userId); + } + } + + public @NonNull List<AssociationInfo> getAssociationsForPackage( + @UserIdInt int userId, @NonNull String packageName) { + final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId); + final List<AssociationInfo> associationsForPackage = + CollectionUtils.filter(associationsForUser, + it -> it.getPackageName().equals(packageName)); + return Collections.unmodifiableList(associationsForPackage); + } + + public @Nullable AssociationInfo getAssociationsForPackageWithAddress( + @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { + final List<AssociationInfo> associations = getAssociationsByAddress(macAddress); + return CollectionUtils.find(associations, + it -> it.belongsToPackage(userId, packageName)); + } + + public @Nullable AssociationInfo getAssociationById(int id) { + synchronized (mLock) { + return mIdMap.get(id); + } + } + + public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) { + final MacAddress address = MacAddress.fromString(macAddress); + + synchronized (mLock) { + final Set<Integer> ids = mAddressMap.get(address); + if (ids == null) return Collections.emptyList(); + + final List<AssociationInfo> associations = new ArrayList<>(); + for (AssociationInfo association : mIdMap.values()) { + if (address.equals(association.getDeviceMacAddress())) { + associations.add(association); + } + } + + return Collections.unmodifiableList(associations); + } + } + + @GuardedBy("mLock") + private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) { + final List<AssociationInfo> cached = mCachedPerUser.get(userId); + if (cached != null) { + return cached; + } + + final List<AssociationInfo> associationsForUser = new ArrayList<>(); + for (AssociationInfo association : mIdMap.values()) { + if (association.getUserId() == userId) { + associationsForUser.add(association); + } + } + final List<AssociationInfo> set = Collections.unmodifiableList(associationsForUser); + mCachedPerUser.set(userId, set); + return set; + } + + @GuardedBy("mLock") + private void invalidateCacheForUserLocked(@UserIdInt int userId) { + mCachedPerUser.delete(userId); + } + + public void registerListener(@NonNull OnChangeListener listener) { + synchronized (mListeners) { + mListeners.add(listener); + } + } + + public void unregisterListener(@NonNull OnChangeListener listener) { + synchronized (mListeners) { + mListeners.remove(listener); + } + } + + private void broadcastChange(@ChangeType int changeType, AssociationInfo association) { + synchronized (mListeners) { + for (OnChangeListener listener : mListeners) { + listener.onAssociationChanged(changeType, association); + + switch (changeType) { + case CHANGE_TYPE_ADDED: + listener.onAssociationAdded(association); + break; + + case CHANGE_TYPE_REMOVED: + listener.onAssociationRemoved(association); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: + listener.onAssociationUpdated(association, true); + break; + + case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: + listener.onAssociationUpdated(association, false); + break; + } + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 9b370dc1f12e..5aa1c933ad87 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -27,15 +27,12 @@ import static android.os.Binder.getCallingUid; import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.getCallingUserId; -import static com.android.internal.util.CollectionUtils.add; import static com.android.internal.util.CollectionUtils.any; -import static com.android.internal.util.CollectionUtils.filter; import static com.android.internal.util.CollectionUtils.find; -import static com.android.internal.util.CollectionUtils.forEach; -import static com.android.internal.util.CollectionUtils.map; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; +import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage; import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId; @@ -45,8 +42,6 @@ import static com.android.server.companion.PermissionsUtils.enforceCallerIsSyste import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation; import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; -import static java.util.Collections.emptySet; -import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MINUTES; @@ -82,7 +77,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.UserInfo; import android.net.MacAddress; import android.net.NetworkPolicyManager; import android.os.Binder; @@ -90,6 +84,7 @@ import android.os.Environment; import android.os.Handler; import android.os.Parcel; import android.os.PowerWhitelistManager; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -111,9 +106,7 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.notification.NotificationAccessConfirmationActivityContract; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; -import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -126,6 +119,7 @@ import java.io.PrintWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -134,12 +128,11 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TimeZone; -import java.util.function.Function; -import java.util.function.Predicate; /** @hide */ @SuppressLint("LongLogTag") -public class CompanionDeviceManagerService extends SystemService { +public class CompanionDeviceManagerService extends SystemService + implements AssociationStore.OnChangeListener { static final String LOG_TAG = "CompanionDeviceManagerService"; static final boolean DEBUG = false; @@ -161,10 +154,11 @@ public class CompanionDeviceManagerService extends SystemService { sDateFormat.setTimeZone(TimeZone.getDefault()); } - private final CompanionDeviceManagerImpl mImpl; // Persistent data store for all Associations. - private final PersistentDataStore mPersistentDataStore; - private final AssociationRequestsProcessor mAssociationRequestsProcessor; + private PersistentDataStore mPersistentStore; + private AssociationStoreImpl mAssociationStore; + private AssociationRequestsProcessor mAssociationRequestsProcessor; + private PowerWhitelistManager mPowerWhitelistManager; private IAppOpsService mAppOpsManager; private BluetoothAdapter mBluetoothAdapter; @@ -183,21 +177,19 @@ public class CompanionDeviceManagerService extends SystemService { mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable(); private ArrayMap<String, TriggerDeviceDisappearedRunnable> mTriggerDeviceDisappearedRunnables = new ArrayMap<>(); + private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = + new RemoteCallbackList<>(); - final Object mLock = new Object(); final Handler mMainHandler = Handler.getMain(); private CompanionDevicePresenceController mCompanionDevicePresenceController; - /** Maps a {@link UserIdInt} to a set of associations for the user. */ - @GuardedBy("mLock") - private final SparseArray<Set<AssociationInfo>> mCachedAssociations = new SparseArray<>(); /** * A structure that consist of two nested maps, and effectively maps (userId + packageName) to * a list of IDs that have been previously assigned to associations for that package. * We maintain this structure so that we never re-use association IDs for the same package * (until it's uninstalled). */ - @GuardedBy("mLock") + @GuardedBy("mPreviouslyUsedIds") private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>(); ActivityTaskManagerInternal mAtmInternal; @@ -206,8 +198,6 @@ public class CompanionDeviceManagerService extends SystemService { public CompanionDeviceManagerService(Context context) { super(context); - mImpl = new CompanionDeviceManagerImpl(); - mPersistentDataStore = new PersistentDataStore(); mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mAppOpsManager = IAppOpsService.Stub.asInterface( @@ -218,49 +208,36 @@ public class CompanionDeviceManagerService extends SystemService { mPermissionControllerManager = requireNonNull( context.getSystemService(PermissionControllerManager.class)); mUserManager = context.getSystemService(UserManager.class); - mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); - mAssociationRequestsProcessor = new AssociationRequestsProcessor(this); - - registerPackageMonitor(); } - private void registerPackageMonitor() { - new PackageMonitor() { - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName); - - clearAssociationForPackage(userId, packageName); - } + @Override + public void onStart() { + mPersistentStore = new PersistentDataStore(); + final Set<AssociationInfo> allAssociations = new ArraySet<>(); - @Override - public void onPackageDataCleared(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName); + synchronized (mPreviouslyUsedIds) { + // The data is stored in DE directories, so we can read the data for all users now + // (which would not be possible if the data was stored to CE directories). + mPersistentStore.readStateForUsers( + mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); + } - clearAssociationForPackage(userId, packageName); - } + mAssociationStore = new AssociationStoreImpl(allAssociations); + mAssociationStore.registerListener(this); - @Override - public void onPackageModified(String packageName) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName); - - forEach(getAssociations(userId, packageName), association -> - updateSpecialAccessPermissionForAssociatedPackage(association)); - } - }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true); - } + mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); + mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mAssociationStore); - @Override - public void onStart() { - publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl); + // Publish "binder service" + final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl(); + publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + registerPackageMonitor(); + // Init Bluetooth mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter != null) { @@ -279,7 +256,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void onUserUnlocking(@NonNull TargetUser user) { final int userId = user.getUserIdentifier(); - final Set<AssociationInfo> associations = getAllAssociationsForUser(userId); + final List<AssociationInfo> associations = mAssociationStore.getAssociationsForUser(userId); if (associations.isEmpty()) return; @@ -290,42 +267,18 @@ public class CompanionDeviceManagerService extends SystemService { MINUTES.toMillis(10)); } - @NonNull - Set<AssociationInfo> getAllAssociationsForUser(@UserIdInt int userId) { - synchronized (mLock) { - readPersistedStateForUserIfNeededLocked(userId); - // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method - // we just called adds an empty set, if there was no previously saved data. - return mCachedAssociations.get(userId); - } - } - - @NonNull - Set<AssociationInfo> getAssociations(@UserIdInt int userId, @NonNull String packageName) { - return filter(getAllAssociationsForUser(userId), - a -> a.belongsToPackage(userId, packageName)); - } - - @Nullable - private AssociationInfo getAssociation(int associationId) { - return find(getAllAssociations(), association -> association.getId() == associationId); - } - - @Nullable - AssociationInfo getAssociation( - @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { - return find(getAssociations(userId, packageName), a -> a.isLinkedTo(macAddress)); - } - @Nullable AssociationInfo getAssociationWithCallerChecks( @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { - return sanitizeWithCallerChecks(getAssociation(userId, packageName, macAddress)); + final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( + userId, packageName, macAddress); + return sanitizeWithCallerChecks(association); } @Nullable AssociationInfo getAssociationWithCallerChecks(int associationId) { - return sanitizeWithCallerChecks(getAssociation(associationId)); + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + return sanitizeWithCallerChecks(association); } @Nullable @@ -341,19 +294,6 @@ public class CompanionDeviceManagerService extends SystemService { return association; } - private Set<AssociationInfo> getAllAssociations() { - final long identity = Binder.clearCallingIdentity(); - try { - final Set<AssociationInfo> result = new ArraySet<>(); - for (UserInfo user : mUserManager.getAliveUsers()) { - result.addAll(getAllAssociationsForUser(user.id)); - } - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - void maybeGrantAutoRevokeExemptions() { Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()"); PackageManager pm = getContext().getPackageManager(); @@ -366,10 +306,8 @@ public class CompanionDeviceManagerService extends SystemService { } try { - Set<AssociationInfo> associations = getAllAssociationsForUser(userId); - if (associations == null) { - continue; - } + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsForUser(userId); for (AssociationInfo a : associations) { try { int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); @@ -384,6 +322,61 @@ public class CompanionDeviceManagerService extends SystemService { } } + @Override + public void onAssociationChanged( + @AssociationStore.ChangeType int changeType, AssociationInfo association) { + final int id = association.getId(); + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + + if (changeType == AssociationStore.CHANGE_TYPE_REMOVED) { + markIdAsPreviouslyUsedForPackage(id, userId, packageName); + } + + final List<AssociationInfo> updatedAssociations = + mAssociationStore.getAssociationsForUser(userId); + final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); + BackgroundThread.getHandler().post(() -> + mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser)); + + // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED. + // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's + // configs, which "listeners" won't (and shouldn't) be able to see. + if (changeType != CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED) { + notifyListeners(userId, updatedAssociations); + } + updateAtm(userId, updatedAssociations); + + restartBleScan(); + } + + private void notifyListeners( + @UserIdInt int userId, @NonNull List<AssociationInfo> associations) { + mListeners.broadcast((listener, callbackUserId) -> { + if ((int) callbackUserId == userId) { + try { + listener.onAssociationsChanged(associations); + } catch (RemoteException ignored) { + } + } + }); + } + + private void markIdAsPreviouslyUsedForPackage( + int associationId, @UserIdInt int userId, @NonNull String packageName) { + synchronized (mPreviouslyUsedIds) { + Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId); + if (usedIdsForUser == null) { + usedIdsForUser = new HashMap<>(); + mPreviouslyUsedIds.put(userId, usedIdsForUser); + } + + final Set<Integer> usedIdsForPackage = + usedIdsForUser.computeIfAbsent(packageName, it -> new HashSet<>()); + usedIdsForPackage.add(associationId); + } + } + class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { @Override @@ -421,8 +414,7 @@ public class CompanionDeviceManagerService extends SystemService { checkUsesFeature(packageName, getCallingUserId()); } - return new ArrayList<>( - CompanionDeviceManagerService.this.getAssociations(userId, packageName)); + return mAssociationStore.getAssociationsForPackage(userId, packageName); } @Override @@ -430,8 +422,7 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerCanInteractWithUserId(getContext(), userId); enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser"); - return new ArrayList<>( - CompanionDeviceManagerService.this.getAllAssociationsForUser(userId)); + return mAssociationStore.getAssociationsForUser(userId); } @Override @@ -441,13 +432,17 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerCanManageCompanionDevice(getContext(), "addOnAssociationsChangedListener"); - //TODO: Implement. + mListeners.register(listener, userId); } @Override public void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId) { - //TODO: Implement. + enforceCallerCanInteractWithUserId(getContext(), userId); + enforceCallerCanManageCompanionDevice( + getContext(), "removeOnAssociationsChangedListener"); + + mListeners.unregister(listener); } @Override @@ -463,7 +458,7 @@ public class CompanionDeviceManagerService extends SystemService { + "(ie. it belongs to a different package or a different user)."); } - disassociateInternal(userId, association.getId()); + disassociateInternal(association.getId()); } @Override @@ -476,7 +471,7 @@ public class CompanionDeviceManagerService extends SystemService { + "or belongs to a different user"); } - disassociateInternal(association.getUserId(), associationId); + disassociateInternal(associationId); } @Override @@ -533,7 +528,7 @@ public class CompanionDeviceManagerService extends SystemService { return true; } - return any(CompanionDeviceManagerService.this.getAssociations(userId, packageName), + return any(mAssociationStore.getAssociationsForPackage(userId, packageName), a -> a.isLinkedTo(macAddress)); } @@ -616,25 +611,18 @@ public class CompanionDeviceManagerService extends SystemService { final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, packageName); - Set<AssociationInfo> deviceAssociations = filter( - CompanionDeviceManagerService.this.getAssociations(userId, packageName), - a -> a.isLinkedTo(deviceAddress)); + final AssociationInfo association = + mAssociationStore.getAssociationsForPackageWithAddress( + userId, packageName, deviceAddress); - if (deviceAssociations.isEmpty()) { + if (association == null) { throw new RemoteException(new DeviceNotAssociatedException("App " + packageName + " is not associated with device " + deviceAddress + " for user " + userId)); } - updateAssociations(associations -> map(associations, association -> { - if (association.belongsToPackage(userId, packageName) - && association.isLinkedTo(deviceAddress)) { - association.setNotifyOnDeviceNearby(active); - } - return association; - }), userId); - - restartBleScan(); + association.setNotifyOnDeviceNearby(active); + mAssociationStore.updateAssociation(association); } @Override @@ -657,14 +645,16 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerIsSystemOr(userId, callingPackage); checkState(!ArrayUtils.isEmpty( - CompanionDeviceManagerService.this.getAssociations(userId, callingPackage)), + mAssociationStore.getAssociationsForPackage(userId, callingPackage)), "App must have an association before calling this API"); checkUsesFeature(callingPackage, userId); } @Override public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) { - final AssociationInfo association = getAssociation(userId, packageName, macAddress); + final AssociationInfo association = + mAssociationStore.getAssociationsForPackageWithAddress( + userId, packageName, macAddress); if (association == null) { return false; } @@ -677,7 +667,8 @@ public class CompanionDeviceManagerService extends SystemService { String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand"); - new CompanionDeviceShellCommand(CompanionDeviceManagerService.this) + new CompanionDeviceShellCommand( + CompanionDeviceManagerService.this, mAssociationStore) .exec(this, in, out, err, args, callback, resultReceiver); } @@ -690,13 +681,8 @@ public class CompanionDeviceManagerService extends SystemService { } fout.append("Companion Device Associations:").append('\n'); - synchronized (mLock) { - for (UserInfo user : getAllUsers()) { - forEach(mCachedAssociations.get(user.id), a -> { - fout.append(" ").append(a.toString()).append('\n'); - }); - } - + for (AssociationInfo a : mAssociationStore.getAssociations()) { + fout.append(" ").append(a.toString()).append('\n'); } fout.append("Currently Connected Devices:").append('\n'); @@ -747,31 +733,56 @@ public class CompanionDeviceManagerService extends SystemService { @Nullable String deviceProfile, boolean selfManaged) { final int id = getNewAssociationIdForPackage(userId, packageName); final long timestamp = System.currentTimeMillis(); + final AssociationInfo association = new AssociationInfo(id, userId, packageName, macAddress, displayName, deviceProfile, selfManaged, false, timestamp); + Slog.i(LOG_TAG, "New CDM association created=" + association); + mAssociationStore.addAssociation(association); updateSpecialAccessPermissionForAssociatedPackage(association); - recordAssociation(association, userId); return association; } - @GuardedBy("mLock") + @NonNull + private Map<String, Set<Integer>> getPreviouslyUsedIdsForUser(@UserIdInt int userId) { + synchronized (mPreviouslyUsedIds) { + return getPreviouslyUsedIdsForUserLocked(userId); + } + } + + @GuardedBy("mPreviouslyUsedIds") + @NonNull + private Map<String, Set<Integer>> getPreviouslyUsedIdsForUserLocked(@UserIdInt int userId) { + final Map<String, Set<Integer>> usedIdsForUser = mPreviouslyUsedIds.get(userId); + if (usedIdsForUser == null) { + return Collections.emptyMap(); + } + return deepUnmodifiableCopy(usedIdsForUser); + } + + @GuardedBy("mPreviouslyUsedIds") @NonNull private Set<Integer> getPreviouslyUsedIdsForPackageLocked( @UserIdInt int userId, @NonNull String packageName) { - final Set<Integer> previouslyUsedIds = mPreviouslyUsedIds.get(userId).get(packageName); - if (previouslyUsedIds != null) return previouslyUsedIds; - return emptySet(); + // "Deeply unmodifiable" map: the map itself and the Set<Integer> values it contains are all + // unmodifiable. + final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUserLocked(userId); + final Set<Integer> usedIdsForPackage = usedIdsForUser.get(packageName); + + if (usedIdsForPackage == null) { + return Collections.emptySet(); + } + + //The set is already unmodifiable. + return usedIdsForPackage; } private int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) { - synchronized (mLock) { - readPersistedStateForUserIfNeededLocked(userId); - + synchronized (mPreviouslyUsedIds) { // First: collect all IDs currently in use for this user's Associations. final SparseBooleanArray usedIds = new SparseBooleanArray(); - for (AssociationInfo it : getAllAssociationsForUser(userId)) { + for (AssociationInfo it : mAssociationStore.getAssociationsForUser(userId)) { usedIds.put(it.getId(), true); } @@ -797,41 +808,14 @@ public class CompanionDeviceManagerService extends SystemService { } } - //TODO also revoke notification access - void disassociateInternal(@UserIdInt int userId, int associationId) { - updateAssociations(associations -> - filterOut(associations, it -> { - if (it.getId() != associationId) return false; - - onAssociationPreRemove(it); - markIdAsPreviouslyUsedForPackage( - it.getId(), it.getUserId(), it.getPackageName()); - return true; - }), userId); - - restartBleScan(); - } - - void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) { - if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName); - - mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId); - updateAssociations(set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)), - userId); - } - - private void markIdAsPreviouslyUsedForPackage( - int associationId, @UserIdInt int userId, @NonNull String packageName) { - synchronized (mLock) { - // Mark as previously used. - readPersistedStateForUserIfNeededLocked(userId); - mPreviouslyUsedIds.get(userId) - .computeIfAbsent(packageName, it -> new HashSet<>()) - .add(associationId); - } + //TODO: also revoke notification access + void disassociateInternal(int associationId) { + onAssociationPreRemove(associationId); + mAssociationStore.removeAssociation(associationId); } - void onAssociationPreRemove(AssociationInfo association) { + void onAssociationPreRemove(int associationId) { + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); if (association.isNotifyOnDeviceNearby() || (association.isSelfManaged() && mPresentSelfManagedDevices.contains(association.getId()))) { @@ -842,7 +826,7 @@ public class CompanionDeviceManagerService extends SystemService { String deviceProfile = association.getDeviceProfile(); if (deviceProfile != null) { AssociationInfo otherAssociationWithDeviceProfile = find( - getAllAssociationsForUser(association.getUserId()), + mAssociationStore.getAssociationsForUser(association.getUserId()), a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile())); if (otherAssociationWithDeviceProfile != null) { Slog.i(LOG_TAG, "Not revoking " + deviceProfile @@ -934,37 +918,7 @@ public class CompanionDeviceManagerService extends SystemService { .getPackageInfoAsUser(packageName, flags , userId)); } - private void recordAssociation(AssociationInfo association, int userId) { - Slog.i(LOG_TAG, "recordAssociation(" + association + ")"); - updateAssociations(associations -> add(associations, association), userId); - } - - private void updateAssociations(Function<Set<AssociationInfo>, Set<AssociationInfo>> update, - int userId) { - synchronized (mLock) { - if (DEBUG) Slog.d(LOG_TAG, "Updating Associations set..."); - - final Set<AssociationInfo> prevAssociations = getAllAssociationsForUser(userId); - if (DEBUG) Slog.d(LOG_TAG, " > Before : " + prevAssociations + "..."); - - final Set<AssociationInfo> updatedAssociations = update.apply( - new ArraySet<>(prevAssociations)); - if (DEBUG) Slog.d(LOG_TAG, " > After: " + updatedAssociations); - - mCachedAssociations.put(userId, unmodifiableSet(updatedAssociations)); - - BackgroundThread.getHandler().sendMessage( - PooledLambda.obtainMessage( - (associations, usedIds) -> - mPersistentDataStore - .persistStateForUser(userId, associations, usedIds), - updatedAssociations, deepCopy(mPreviouslyUsedIds.get(userId)))); - - updateAtm(userId, updatedAssociations); - } - } - - private void updateAtm(int userId, Set<AssociationInfo> associations) { + private void updateAtm(int userId, List<AssociationInfo> associations) { final Set<Integer> companionAppUids = new ArraySet<>(); for (AssociationInfo association : associations) { final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(), @@ -979,50 +933,18 @@ public class CompanionDeviceManagerService extends SystemService { } } - @GuardedBy("mLock") - private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) { - if (mCachedAssociations.get(userId) != null) return; - - Slog.i(LOG_TAG, "Reading state for user " + userId + " from the disk"); - - final Set<AssociationInfo> associations = new ArraySet<>(); - final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>(); - mPersistentDataStore.readStateForUser(userId, associations, previouslyUsedIds); - - if (DEBUG) { - Slog.d(LOG_TAG, " > associations=" + associations + "\n" - + " > previouslyUsedIds=" + previouslyUsedIds); - } - - mCachedAssociations.put(userId, unmodifiableSet(associations)); - mPreviouslyUsedIds.append(userId, previouslyUsedIds); - } - - private List<UserInfo> getAllUsers() { - final long identity = Binder.clearCallingIdentity(); - try { - return mUserManager.getUsers(); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - void onDeviceConnected(String address) { Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); mCurrentlyConnectedDevices.add(address); - for (UserInfo user : getAllUsers()) { - for (AssociationInfo association : getAllAssociationsForUser(user.id)) { - if (association.isLinkedTo(address)) { - if (association.getDeviceProfile() != null) { - Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile() - + " to " + association.getPackageName() - + " due to device connected: " + association.getDeviceMacAddress()); + for (AssociationInfo association : mAssociationStore.getAssociationsByAddress(address)) { + if (association.getDeviceProfile() != null) { + Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile() + + " to " + association.getPackageName() + + " due to device connected: " + association.getDeviceMacAddress()); - addRoleHolderForAssociation(getContext(), association); - } - } + addRoleHolderForAssociation(getContext(), association); } } @@ -1115,7 +1037,9 @@ public class CompanionDeviceManagerService extends SystemService { Date lastNearby = mDevicesLastNearby.valueAt(i); if (isDeviceDisappeared(lastNearby)) { - for (AssociationInfo association : getAllAssociations(address)) { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(address); + for (AssociationInfo association : associations) { if (association.isNotifyOnDeviceNearby()) { mCompanionDevicePresenceController.unbindDevicePresenceListener( association.getPackageName(), association.getUserId()); @@ -1157,20 +1081,6 @@ public class CompanionDeviceManagerService extends SystemService { } } - private Set<AssociationInfo> getAllAssociations(String deviceAddress) { - List<UserInfo> aliveUsers = mUserManager.getAliveUsers(); - Set<AssociationInfo> result = new ArraySet<>(); - for (int i = 0, size = aliveUsers.size(); i < size; i++) { - UserInfo user = aliveUsers.get(i); - for (AssociationInfo association : getAllAssociationsForUser(user.id)) { - if (association.isLinkedTo(deviceAddress)) { - result.add(association); - } - } - } - return result; - } - private void onDeviceNearby(String address) { Date timestamp = new Date(); Date oldTimestamp = mDevicesLastNearby.put(address, timestamp); @@ -1186,7 +1096,9 @@ public class CompanionDeviceManagerService extends SystemService { || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS; if (justAppeared) { Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")"); - for (AssociationInfo association : getAllAssociations(address)) { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(address); + for (AssociationInfo association : associations) { if (association.isNotifyOnDeviceNearby()) { mCompanionDevicePresenceController.onDeviceNotifyAppeared(association, getContext(), mMainHandler); @@ -1199,7 +1111,9 @@ public class CompanionDeviceManagerService extends SystemService { Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")"); boolean hasDeviceListeners = false; - for (AssociationInfo association : getAllAssociations(address)) { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(address); + for (AssociationInfo association : associations) { if (association.isNotifyOnDeviceNearby()) { mCompanionDevicePresenceController.onDeviceNotifyDisappeared( association, getContext(), mMainHandler); @@ -1272,7 +1186,7 @@ public class CompanionDeviceManagerService extends SystemService { private List<ScanFilter> getBleScanFilters() { ArrayList<ScanFilter> result = new ArrayList<>(); ArraySet<String> addressesSeen = new ArraySet<>(); - for (AssociationInfo association : getAllAssociations()) { + for (AssociationInfo association : mAssociationStore.getAssociations()) { if (association.isSelfManaged()) { continue; } @@ -1312,17 +1226,6 @@ public class CompanionDeviceManagerService extends SystemService { } } - private static @NonNull <T> Set<T> filterOut( - @NonNull Set<T> set, @NonNull Predicate<? super T> predicate) { - return CollectionUtils.filter(set, predicate.negate()); - } - - private Map<String, Set<Integer>> deepCopy(Map<String, Set<Integer>> orig) { - final Map<String, Set<Integer>> copy = new HashMap<>(orig.size(), 1f); - forEach(orig, (key, value) -> copy.put(key, new ArraySet<>(value))); - return copy; - } - void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) { if (getCallingUid() == SYSTEM_UID) return; @@ -1337,4 +1240,61 @@ public class CompanionDeviceManagerService extends SystemService { + FEATURE_COMPANION_DEVICE_SETUP + " in manifest to use this API"); } + + private void registerPackageMonitor() { + new PackageMonitor() { + @Override + public void onPackageRemoved(String packageName, int uid) { + final int userId = getChangingUserId(); + Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName); + + clearAssociationForPackage(userId, packageName); + } + + @Override + public void onPackageDataCleared(String packageName, int uid) { + final int userId = getChangingUserId(); + Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName); + + clearAssociationForPackage(userId, packageName); + } + + @Override + public void onPackageModified(String packageName) { + final int userId = getChangingUserId(); + Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName); + + final List<AssociationInfo> associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + updateSpecialAccessPermissionForAssociatedPackage(association); + } + } + }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true); + } + + private void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName); + + // First, unbind CompanionService if needed. + mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId); + + // Clear associations. + final List<AssociationInfo> associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + mAssociationStore.removeAssociation(association.getId()); + } + } + + private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) { + final Map<String, Set<Integer>> copy = new HashMap<>(); + + for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) { + final Set<Integer> valueCopy = new HashSet<>(entry.getValue()); + copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy)); + } + + return Collections.unmodifiableMap(copy); + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 5cb30797c0f4..5c0571d801aa 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -16,7 +16,6 @@ package com.android.server.companion; -import static com.android.internal.util.CollectionUtils.forEach; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; import android.companion.AssociationInfo; @@ -24,24 +23,33 @@ import android.util.Log; import android.util.Slog; import java.io.PrintWriter; +import java.util.List; class CompanionDeviceShellCommand extends android.os.ShellCommand { private final CompanionDeviceManagerService mService; + private final AssociationStore mAssociationStore; - CompanionDeviceShellCommand(CompanionDeviceManagerService service) { + CompanionDeviceShellCommand(CompanionDeviceManagerService service, + AssociationStore associationStore) { mService = service; + mAssociationStore = associationStore; } @Override public int onCommand(String cmd) { + final PrintWriter out = getOutPrintWriter(); try { switch (cmd) { case "list": { - forEach( - mService.getAllAssociationsForUser(getNextArgInt()), - a -> getOutPrintWriter() - .println(a.getPackageName() + " " - + a.getDeviceMacAddress())); + final int userId = getNextArgInt(); + final List<AssociationInfo> associationsForUser = + mAssociationStore.getAssociationsForUser(userId); + for (AssociationInfo association : associationsForUser) { + // TODO(b/212535524): use AssociationInfo.toShortString(), once it's not + // longer referenced in tests. + out.println(association.getPackageName() + " " + + association.getDeviceMacAddress()); + } } break; @@ -60,7 +68,7 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { final AssociationInfo association = mService.getAssociationWithCallerChecks(userId, packageName, address); if (association != null) { - mService.disassociateInternal(userId, association.getId()); + mService.disassociateInternal(association.getId()); } } break; diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index 87558dfd4ffd..97ec3bb7127d 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -33,15 +33,18 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.companion.AssociationInfo; +import android.content.pm.UserInfo; import android.net.MacAddress; import android.os.Environment; +import android.util.ArrayMap; import android.util.AtomicFile; -import android.util.ExceptionUtils; import android.util.Slog; +import android.util.SparseArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; +import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -50,8 +53,11 @@ import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -68,8 +74,8 @@ import java.util.concurrent.ConcurrentMap; * * Before Android T the data was stored using the v0 schema. * - * @see #readAssociationsV0(TypedXmlPullParser, int, Set) - * @see #readAssociationV0(TypedXmlPullParser, int, int, Set) + * @see #readAssociationsV0(TypedXmlPullParser, int, Collection) + * @see #readAssociationV0(TypedXmlPullParser, int, int, Collection) * * The following snippet is a sample of a the file that is using v0 schema. * <pre>{@code @@ -100,8 +106,8 @@ import java.util.concurrent.ConcurrentMap; * optional. * * @see #CURRENT_PERSISTENCE_VERSION - * @see #readAssociationsV1(TypedXmlPullParser, int, Set) - * @see #readAssociationV1(TypedXmlPullParser, int, Set) + * @see #readAssociationsV1(TypedXmlPullParser, int, Collection) + * @see #readAssociationV1(TypedXmlPullParser, int, Collection) * @see #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) * * The following snippet is a sample of a the file that is using v0 schema. @@ -168,6 +174,23 @@ final class PersistentDataStore { private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile = new ConcurrentHashMap<>(); + void readStateForUsers(@NonNull List<UserInfo> users, + @NonNull Set<AssociationInfo> allAssociationsOut, + @NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) { + for (UserInfo user : users) { + final int userId = user.id; + // Previously used IDs are stored in the "out" collection per-user. + final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>(); + + // Associations for all users are stored in a single "flat" set: so we read directly + // into it. + readStateForUser(userId, allAssociationsOut, previouslyUsedIds); + + // Save previously used IDs for this user into the "out" structure. + previouslyUsedIdsPerUserOut.append(userId, previouslyUsedIds); + } + } + /** * Reads previously persisted data for the given user "into" the provided containers. * @@ -176,7 +199,7 @@ final class PersistentDataStore { * @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into". */ void readStateForUser(@UserIdInt int userId, - @NonNull Set<AssociationInfo> associationsOut, + @NonNull Collection<AssociationInfo> associationsOut, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) { Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk"); final AtomicFile file = getStorageFileForUser(userId); @@ -237,7 +260,8 @@ final class PersistentDataStore { * @param associations a set of user's associations. * @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user. */ - void persistStateForUser(@UserIdInt int userId, @NonNull Set<AssociationInfo> associations, + void persistStateForUser(@UserIdInt int userId, + @NonNull Collection<AssociationInfo> associations, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) { Slog.i(LOG_TAG, "Writing associations for user " + userId + " to disk"); if (DEBUG) Slog.d(LOG_TAG, " > " + associations); @@ -250,7 +274,7 @@ final class PersistentDataStore { } private int readStateFromFileLocked(@UserIdInt int userId, @NonNull AtomicFile file, - @NonNull String rootTag, @Nullable Set<AssociationInfo> associationsOut, + @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) { try (FileInputStream in = file.openRead()) { final TypedXmlPullParser parser = Xml.resolvePullParser(in); @@ -282,28 +306,25 @@ final class PersistentDataStore { } private void persistStateToFileLocked(@NonNull AtomicFile file, - @Nullable Set<AssociationInfo> associations, + @Nullable Collection<AssociationInfo> associations, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) { - file.write(out -> { - try { - final TypedXmlSerializer serializer = Xml.resolveSerializer(out); - serializer.setFeature( - "http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startDocument(null, true); - serializer.startTag(null, XML_TAG_STATE); - writeIntAttribute(serializer, - XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION); - - writeAssociations(serializer, associations); - writePreviouslyUsedIds(serializer, previouslyUsedIdsPerPackage); - - serializer.endTag(null, XML_TAG_STATE); - serializer.endDocument(); - } catch (Exception e) { - Slog.e(LOG_TAG, "Error while writing associations file", e); - throw ExceptionUtils.propagate(e); - } + // Writing to file could fail, for example, if the user has been recently removed and so was + // their DE (/data/system_de/<user-id>/) directory. + writeToFileSafely(file, out -> { + final TypedXmlSerializer serializer = Xml.resolveSerializer(out); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startDocument(null, true); + serializer.startTag(null, XML_TAG_STATE); + writeIntAttribute(serializer, + XML_ATTR_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION); + + writeAssociations(serializer, associations); + writePreviouslyUsedIds(serializer, previouslyUsedIdsPerPackage); + + serializer.endTag(null, XML_TAG_STATE); + serializer.endDocument(); }); } @@ -321,7 +342,7 @@ final class PersistentDataStore { } private static void readAssociationsV0(@NonNull TypedXmlPullParser parser, - @UserIdInt int userId, @NonNull Set<AssociationInfo> out) + @UserIdInt int userId, @NonNull Collection<AssociationInfo> out) throws XmlPullParserException, IOException { requireStartOfTag(parser, XML_TAG_ASSOCIATIONS); @@ -342,7 +363,8 @@ final class PersistentDataStore { } private static void readAssociationV0(@NonNull TypedXmlPullParser parser, @UserIdInt int userId, - int associationId, @NonNull Set<AssociationInfo> out) throws XmlPullParserException { + int associationId, @NonNull Collection<AssociationInfo> out) + throws XmlPullParserException { requireStartOfTag(parser, XML_TAG_ASSOCIATION); final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE); @@ -360,7 +382,7 @@ final class PersistentDataStore { } private static void readAssociationsV1(@NonNull TypedXmlPullParser parser, - @UserIdInt int userId, @NonNull Set<AssociationInfo> out) + @UserIdInt int userId, @NonNull Collection<AssociationInfo> out) throws XmlPullParserException, IOException { requireStartOfTag(parser, XML_TAG_ASSOCIATIONS); @@ -374,7 +396,7 @@ final class PersistentDataStore { } private static void readAssociationV1(@NonNull TypedXmlPullParser parser, @UserIdInt int userId, - @NonNull Set<AssociationInfo> out) throws XmlPullParserException, IOException { + @NonNull Collection<AssociationInfo> out) throws XmlPullParserException, IOException { requireStartOfTag(parser, XML_TAG_ASSOCIATION); final int associationId = readIntAttribute(parser, XML_ATTR_ID); @@ -421,9 +443,11 @@ final class PersistentDataStore { } private static void writeAssociations(@NonNull XmlSerializer parent, - @Nullable Set<AssociationInfo> associations) throws IOException { + @Nullable Collection<AssociationInfo> associations) throws IOException { final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS); - forEach(associations, it -> writeAssociation(serializer, it)); + for (AssociationInfo association : associations) { + writeAssociation(serializer, association); + } serializer.endTag(null, XML_TAG_ASSOCIATIONS); } @@ -498,4 +522,13 @@ final class PersistentDataStore { } return associationInfo; } + + private static void writeToFileSafely(@NonNull AtomicFile file, + @NonNull ThrowingConsumer<FileOutputStream> consumer) { + try { + file.write(consumer); + } catch (Exception e) { + Slog.e(LOG_TAG, "Error while writing to file " + file, e); + } + } } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 6fe2806e8139..9b2948f42ed8 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -50,6 +50,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageApi; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.mutate.PackageStateMutator; import java.io.IOException; import java.lang.annotation.Retention; @@ -885,17 +886,6 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP public abstract boolean userNeedsBadging(int userId); /** - * Perform the given action for each package. - * Note that packages lock will be held while performing the actions. - * - * If the caller does not need all packages, prefer the potentially non-locking - * {@link #withPackageSettingsSnapshot(Consumer)}. - * - * @param actionLocked action to be performed - */ - public abstract void forEachPackage(Consumer<AndroidPackage> actionLocked); - - /** * Perform the given action for each {@link PackageSetting}. * Note that packages lock will be held while performing the actions. * @@ -918,12 +908,24 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP public abstract void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action); /** + * {@link #forEachPackageState(boolean, Consumer)} but filtered to only states with packages + * on device where {@link PackageStateInternal#getPkg()} is not null. + */ + public abstract void forEachPackage(Consumer<AndroidPackage> action); + + /** * Perform the given action for each installed package for a user. * Note that packages lock will be held while performing the actions. */ public abstract void forEachInstalledPackage( @NonNull Consumer<AndroidPackage> actionLocked, @UserIdInt int userId); + /** + * Perform the given action for each installed package for a user. + */ + public abstract void forEachInstalledPackage(boolean locked, + @NonNull Consumer<AndroidPackage> action, @UserIdInt int userId); + /** Returns the list of enabled components */ public abstract ArraySet<String> getEnabledComponents(String packageName, int userId); @@ -1265,4 +1267,62 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP */ public abstract void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags, boolean migrateAppsData); + + /** + * Initiates a package state mutation request, returning the current state as known by + * PackageManager. This allows the later commit request to compare the initial values and + * determine if any state was changed or any packages were updated since the whole request + * was initiated. + * + * As a concrete example, consider the following steps: + * <ol> + * <li>Read a package state without taking a lock</li> + * <li>Check some values in that state, determine that a mutation needs to occur</li> + * <li>Call to commit the change with the new value, takes lock</li> + * </ol> + * + * Between steps 1 and 3, because the lock was not taken for the entire flow, it's possible + * a package state was changed by another consumer or a package was updated/installed. + * + * If anything has changed, + * {@link #commitPackageStateMutation(PackageStateMutator.InitialState, Consumer)} will return + * a {@link PackageStateMutator.Result} indicating so. If the caller has not indicated it can + * ignore changes, it can opt to re-run the commit logic from the top with a true write lock + * around all of its read-logic-commit loop. + * + * Note that if the caller does not care about potential race conditions or package/state + * changes between steps 1 and 3, it can simply opt to not call this method and pass in null + * for the initial state. This is useful to avoid long running data structure locks when the + * caller is changing a value as part of a one-off request. Perhaps from an app side API which + * mutates only a single package, where it doesn't care what the state of that package is or + * any other packages on the devices. + * + * Important to note is that if no locking is enforced, callers themselves will not be + * synchronized with themselves. The caller may be relying on the PackageManager lock to + * enforce ordering within their own code path, and that has to be adjusted if migrated off + * the lock. + */ + @NonNull + public abstract PackageStateMutator.InitialState recordInitialState(); + + /** + * Some questions to ask when designing a mutation: + * <ol> + * <li>What external system state is required and is it synchronized properly?</li> + * <li>Are there any package/state changes that could happen to the target (or another) + * package that could result in the commit being invalid?</li> + * <li>Is the caller synchronized with itself and can handle multiple mutations being + * requested from different threads?</li> + * <li>What should be done in case of a conflict and the commit can't be finished?</li> + * </ol> + * + * @param state See {@link #recordInitialState()}. If null, no result is returned. + * @param consumer Lean wrapper around just the logic that changes state values + * @return result if anything changed since initial state, or null if nothing changed and + * commit was successful + */ + @Nullable + public abstract PackageStateMutator.Result commitPackageStateMutation( + @Nullable PackageStateMutator.InitialState state, + @NonNull Consumer<PackageStateMutator> consumer); } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 6e05841230af..262933dea27f 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -45,6 +45,7 @@ import android.bluetooth.IBluetoothManager; import android.bluetooth.IBluetoothManagerCallback; import android.bluetooth.IBluetoothProfileServiceConnection; import android.bluetooth.IBluetoothStateChangeCallback; +import android.bluetooth.IBluetoothLeCallControl; import android.content.ActivityNotFoundException; import android.content.AttributionSource; import android.content.BroadcastReceiver; @@ -109,10 +110,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final String BLUETOOTH_PRIVILEGED = android.Manifest.permission.BLUETOOTH_PRIVILEGED; - private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid"; - private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS = "bluetooth_address"; - private static final String SECURE_SETTINGS_BLUETOOTH_NAME = "bluetooth_name"; - private static final int ACTIVE_LOG_MAX_SIZE = 20; private static final int CRASH_LOG_MAX_SIZE = 100; @@ -640,7 +637,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mContext.getResources() .getBoolean(com.android.internal.R.bool.config_bluetooth_address_validation) && Settings.Secure.getIntForUser(mContentResolver, - SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 0, mUserId) + Settings.Secure.BLUETOOTH_NAME, 0, mUserId) == 0) { // if the valid flag is not set, don't load the address and name if (DBG) { @@ -649,9 +646,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return; } mName = Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, mUserId); + mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId); mAddress = Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, mUserId); + mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId); if (DBG) { Slog.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress); } @@ -665,30 +662,30 @@ class BluetoothManagerService extends IBluetoothManager.Stub { */ private void storeNameAndAddress(String name, String address) { if (name != null) { - Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, name, + Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name, mUserId); mName = name; if (DBG) { Slog.d(TAG, "Stored Bluetooth name: " + Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, + mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId)); } } if (address != null) { - Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, + Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, address, mUserId); mAddress = address; if (DBG) { Slog.d(TAG, "Stored Bluetoothaddress: " + Settings.Secure.getStringForUser( - mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, + mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId)); } } if ((name != null) && (address != null)) { - Settings.Secure.putIntForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 1, + Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1, mUserId); } } @@ -1327,11 +1324,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { + bluetoothProfile); } - if (bluetoothProfile != BluetoothProfile.HEADSET) { + Intent intent; + if (bluetoothProfile == BluetoothProfile.HEADSET) { + intent = new Intent(IBluetoothHeadset.class.getName()); + } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) { + intent = new Intent(IBluetoothLeCallControl.class.getName()); + } else { return false; } - Intent intent = new Intent(IBluetoothHeadset.class.getName()); psc = new ProfileServiceConnections(intent); if (!psc.bindService()) { return false; diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index c7f4b4d03648..780afd86b373 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -80,6 +80,7 @@ import android.content.res.ObbInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.DropBoxManager; import android.os.Environment; import android.os.Handler; @@ -1648,7 +1649,8 @@ class StorageManagerService extends IStorageManager.Stub // obb data to its new location. This may take time depending on the size of // the data to be copied so it's done on the StorageManager worker thread. // This needs to be finished before start mounting obb directories. - if (userId == 0) { + if (userId == 0 + && Build.VERSION.DEVICE_INITIAL_SDK_INT < Build.VERSION_CODES.Q) { mPmInternal.migrateLegacyObbData(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f0f6bd172770..9d2b4e7a570f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -189,6 +189,7 @@ import android.app.ProfilerInfo; import android.app.PropertyInvalidatedCache; import android.app.SyncNotedAppOp; import android.app.WaitResult; +import android.app.WtfException; import android.app.backup.BackupManager.OperationType; import android.app.backup.IBackupManager; import android.app.compat.CompatChanges; @@ -5822,7 +5823,7 @@ public class ActivityManagerService extends IActivityManager.Stub return Arrays.binarySearch(allowlist, appId) >= 0 || Arrays.binarySearch(mDeviceIdleTempAllowlist, appId) >= 0 - || mPendingTempAllowlist.indexOfKey(uid) >= 0; + || mPendingTempAllowlist.get(uid) != null; } /** @@ -12634,6 +12635,7 @@ public class ActivityManagerService extends IActivityManager.Stub int callingUid; int callingPid; boolean instantApp; + boolean throwWtfException = false; synchronized(this) { if (caller != null) { callerApp = getRecordForAppLOSP(caller); @@ -12728,13 +12730,9 @@ public class ActivityManagerService extends IActivityManager.Stub + "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"); + // will be removed when enforcement is required // Assume default behavior-- flag check is not enforced + throwWtfException = true; flags |= Context.RECEIVER_EXPORTED; } } else if (!requireExplicitFlagForDynamicReceivers) { @@ -12865,6 +12863,15 @@ public class ActivityManagerService extends IActivityManager.Stub } } + if (throwWtfException) { + throw new WtfException( + 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"); + } + return sticky; } } @@ -15115,11 +15122,13 @@ public class ActivityManagerService extends IActivityManager.Stub // First copy out the pending changes... we need to leave them in the map for now, // in case someone needs to check what is coming up while we don't have the lock held. - synchronized (mProcLock) { - N = mPendingTempAllowlist.size(); - list = new PendingTempAllowlist[N]; - for (int i = 0; i < N; i++) { - list[i] = mPendingTempAllowlist.valueAt(i); + synchronized (this) { + synchronized (mProcLock) { + N = mPendingTempAllowlist.size(); + list = new PendingTempAllowlist[N]; + for (int i = 0; i < N; i++) { + list[i] = mPendingTempAllowlist.valueAt(i); + } } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 0c94fbbcf74f..c55bbe8e971b 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -920,7 +920,7 @@ public final class CachedAppOptimizer { void unfreezeTemporarily(ProcessRecord app) { if (mUseFreezer) { synchronized (mProcLock) { - if (app.mOptRecord.isFrozen()) { + if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) { unfreezeAppLSP(app); freezeAppAsyncLSP(app); } diff --git a/services/core/java/com/android/server/am/PendingTempAllowlists.java b/services/core/java/com/android/server/am/PendingTempAllowlists.java index 75935c4f22fa..0263de7ee586 100644 --- a/services/core/java/com/android/server/am/PendingTempAllowlists.java +++ b/services/core/java/com/android/server/am/PendingTempAllowlists.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.os.Process.INVALID_UID; + import android.util.SparseArray; /** Allowlists of uids to temporarily bypass Power Save mode. */ @@ -31,29 +33,42 @@ final class PendingTempAllowlists { } void put(int uid, ActivityManagerService.PendingTempAllowlist value) { - mPendingTempAllowlist.put(uid, value); + synchronized (mPendingTempAllowlist) { + mPendingTempAllowlist.put(uid, value); + } mService.mAtmInternal.onUidAddedToPendingTempAllowlist(uid, value.tag); } void removeAt(int index) { - final int uid = mPendingTempAllowlist.keyAt(index); - mPendingTempAllowlist.removeAt(index); + int uid = INVALID_UID; + synchronized (mPendingTempAllowlist) { + uid = mPendingTempAllowlist.keyAt(index); + mPendingTempAllowlist.removeAt(index); + } mService.mAtmInternal.onUidRemovedFromPendingTempAllowlist(uid); } ActivityManagerService.PendingTempAllowlist get(int uid) { - return mPendingTempAllowlist.get(uid); + synchronized (mPendingTempAllowlist) { + return mPendingTempAllowlist.get(uid); + } } int size() { - return mPendingTempAllowlist.size(); + synchronized (mPendingTempAllowlist) { + return mPendingTempAllowlist.size(); + } } ActivityManagerService.PendingTempAllowlist valueAt(int index) { - return mPendingTempAllowlist.valueAt(index); + synchronized (mPendingTempAllowlist) { + return mPendingTempAllowlist.valueAt(index); + } } int indexOfKey(int key) { - return mPendingTempAllowlist.indexOfKey(key); + synchronized (mPendingTempAllowlist) { + return mPendingTempAllowlist.indexOfKey(key); + } } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 2def50efee60..1ad0bcea711c 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -135,6 +135,7 @@ import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ProcessChangeItem; import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.DexManager; +import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowManagerService; @@ -2379,6 +2380,8 @@ public final class ProcessList { final String[] targetPackagesList = sharedPackages.length == 0 ? new String[]{app.info.packageName} : sharedPackages; + final boolean hasAppStorage = hasAppStorage(pmInt, app.info.packageName); + pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, targetPackagesList, uid); if (pkgDataInfoMap == null) { // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a @@ -2401,6 +2404,12 @@ public final class ProcessList { bindMountAppsData = false; } + if (!hasAppStorage) { + bindMountAppsData = false; + pkgDataInfoMap = null; + allowlistedAppDataInfoMap = null; + } + int userId = UserHandle.getUserId(uid); StorageManagerInternal storageManagerInternal = LocalServices.getService( StorageManagerInternal.class); @@ -2488,6 +2497,17 @@ public final class ProcessList { } } + private boolean hasAppStorage(PackageManagerInternal pmInt, String packageName) { + final AndroidPackage pkg = pmInt.getPackage(packageName); + if (pkg == null) { + Slog.w(TAG, "Unknown package " + packageName); + return false; + } + final PackageManager.Property noAppStorageProp = + pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); + return noAppStorageProp == null || !noAppStorageProp.getBoolean(); + } + @GuardedBy("mService") void startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, int zygotePolicyFlags) { startProcessLocked(app, hostingRecord, zygotePolicyFlags, null /* abiOverride */); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 8615393dbfec..0879bec665ba 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3239,6 +3239,13 @@ public class AudioService extends IAudioService.Stub } } + private void enforceQueryStatePermission() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing QUERY_AUDIO_STATE permissions"); + } + } + private void enforceQueryStateOrModifyRoutingPermission() { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED @@ -4143,6 +4150,7 @@ public class AudioService extends IAudioService.Stub /** Get last audible volume before stream was muted. */ public int getLastAudibleStreamVolume(int streamType) { + enforceQueryStatePermission(); ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); return (mStreamStates[streamType].getIndex(device) + 5) / 10; @@ -9771,7 +9779,7 @@ public class AudioService extends IAudioService.Stub projection)) { Slog.w(TAG, "Permission denied to register audio policy for pid " + Binder.getCallingPid() + " / uid " + Binder.getCallingUid() - + ", need MODIFY_AUDIO_ROUTING or MediaProjection that can project audio"); + + ", need system permission or a MediaProjection that can project audio"); return null; } diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 2465ec5a4ca9..6f7176816ddb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -38,8 +38,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement private static final String TAG = "Biometrics/AcquisitionClient"; - 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 static final VibrationEffect SUCCESS_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); @@ -196,7 +196,7 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement getContext().getOpPackageName(), SUCCESS_VIBRATION_EFFECT, getClass().getSimpleName() + "::success", - TOUCH_VIBRATION_ATTRIBUTES); + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); } } @@ -207,7 +207,7 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement getContext().getOpPackageName(), ERROR_VIBRATION_EFFECT, getClass().getSimpleName() + "::error", - TOUCH_VIBRATION_ATTRIBUTES); + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); } } } diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java index df95bf597dc0..12203919a5e7 100644 --- a/services/core/java/com/android/server/communal/CommunalManagerService.java +++ b/services/core/java/com/android/server/communal/CommunalManagerService.java @@ -341,7 +341,8 @@ public final class CommunalManagerService extends SystemService { UserHandle.SYSTEM, mIntentFilter, /* broadcastPermission= */null, - /* scheduler= */ null); + /* scheduler= */ null, + Context.RECEIVER_EXPORTED_UNAUDITED); } private void unregister() { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index a2fed291cb38..6a716cbc2816 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1461,7 +1461,10 @@ public class Vpn { // parameters. If that fails, disconnect. if (oldConfig != null && updateLinkPropertiesInPlaceIfPossible(mNetworkAgent, oldConfig)) { - // Keep mNetworkAgent unchanged + // Update underlying networks if it is changed. + if (!Arrays.equals(oldConfig.underlyingNetworks, config.underlyingNetworks)) { + setUnderlyingNetworks(config.underlyingNetworks); + } } else { // Initialize the state for a new agent, while keeping the old one connected // in case this new connection fails. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index c0a6abf3a121..c4f2b14e335b 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -714,7 +714,6 @@ public final class DisplayManagerService extends SystemService { display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher; if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) { handleLogicalDisplayChangedLocked(display); - scheduleTraversalLocked(false); } } } diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index d2baaf2228a1..d632ee3d021c 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -147,7 +147,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { getContext(), getHandler(), mWifiDisplayListener); getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, - new IntentFilter(ACTION_DISCONNECT), null, mHandler); + new IntentFilter(ACTION_DISCONNECT), null, mHandler, + Context.RECEIVER_NOT_EXPORTED); } }); } diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java index 14616754e160..9e00f95c9c6f 100644 --- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java +++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java @@ -509,7 +509,7 @@ public abstract class AbstractMasterSystemService<M extends AbstractMasterSystem * * <pre><code> * resolver.registerContentObserver(Settings.Global.getUriFor( - * Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, observer, + * Settings.Global.AUTOFILL_LOGGING_LEVEL), false, observer, * UserHandle.USER_ALL); * </code></pre> * diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java index 9fa6fad4f926..773dc680d4cb 100644 --- a/services/core/java/com/android/server/input/InputShellCommand.java +++ b/services/core/java/com/android/server/input/InputShellCommand.java @@ -560,7 +560,12 @@ public class InputShellCommand extends ShellCommand { sleep(duration); for (KeyEvent event: events) { - injectKeyEventAsync(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); + final int keyCode = event.getKeyCode(); + final KeyEvent upEvent = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, + 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, + inputSource); + injectKeyEventAsync(upEvent); + metaState &= ~MODIFIER.getOrDefault(keyCode, 0); } } diff --git a/services/core/java/com/android/server/inputmethod/ImfLock.java b/services/core/java/com/android/server/inputmethod/ImfLock.java new file mode 100644 index 000000000000..612c14faa571 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/ImfLock.java @@ -0,0 +1,45 @@ +/* + * 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.server.inputmethod; + +/** + * The implicit lock of this class serves as the global lock for + * the {@link InputMethodManagerService} and its controllers, + * which contain the main logic of the input method framework (IMF). + * + * <p> + * This lock can be used as follows in code: + * <pre> + * synchronized (ImfLock.class) { + * ... + * } + * </pre> + * + * <p> + * For annotations, you can use a similar syntax: + * <pre> + * @GuardedBy("ImfLock.class") + * myMethodDeclaration() { + * ... + * } + * </pre> + */ +final class ImfLock { + private ImfLock() { + // no instances + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index c5dc23e30822..220d790d1208 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -72,16 +72,16 @@ final class InputMethodBindingController { @NonNull private final WindowManagerInternal mWindowManagerInternal; @NonNull private final Resources mRes; - private long mLastBindTime; - private boolean mHasConnection; - @Nullable private String mCurId; - @Nullable private String mSelectedMethodId; - @Nullable private Intent mCurIntent; - @Nullable private IInputMethod mCurMethod; - private int mCurMethodUid = Process.INVALID_UID; - private IBinder mCurToken; - private int mCurSeq; - private boolean mVisibleBound; + @GuardedBy("ImfLock.class") private long mLastBindTime; + @GuardedBy("ImfLock.class") private boolean mHasConnection; + @GuardedBy("ImfLock.class") @Nullable private String mCurId; + @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId; + @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent; + @GuardedBy("ImfLock.class") @Nullable private IInputMethod mCurMethod; + @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID; + @GuardedBy("ImfLock.class") private IBinder mCurToken; + @GuardedBy("ImfLock.class") private int mCurSeq; + @GuardedBy("ImfLock.class") private boolean mVisibleBound; private boolean mSupportsStylusHw; /** @@ -146,6 +146,7 @@ final class InputMethodBindingController { * Time that we last initiated a bind to the input method, to determine * if we should try to disconnect and reconnect to it. */ + @GuardedBy("ImfLock.class") long getLastBindTime() { return mLastBindTime; } @@ -154,6 +155,7 @@ final class InputMethodBindingController { * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). */ + @GuardedBy("ImfLock.class") boolean hasConnection() { return mHasConnection; } @@ -166,6 +168,7 @@ final class InputMethodBindingController { * * @see #getSelectedMethodId() */ + @GuardedBy("ImfLock.class") @Nullable String getCurId() { return mCurId; @@ -184,11 +187,13 @@ final class InputMethodBindingController { * * @see #getCurId() */ + @GuardedBy("ImfLock.class") @Nullable String getSelectedMethodId() { return mSelectedMethodId; } + @GuardedBy("ImfLock.class") void setSelectedMethodId(@Nullable String selectedMethodId) { mSelectedMethodId = selectedMethodId; } @@ -197,6 +202,7 @@ final class InputMethodBindingController { * The token we have made for the currently active input method, to * identify it in the future. */ + @GuardedBy("ImfLock.class") IBinder getCurToken() { return mCurToken; } @@ -204,6 +210,7 @@ final class InputMethodBindingController { /** * The Intent used to connect to the current input method. */ + @GuardedBy("ImfLock.class") @Nullable Intent getCurIntent() { return mCurIntent; @@ -213,6 +220,7 @@ final class InputMethodBindingController { * The current binding sequence number, incremented every time there is * a new bind performed. */ + @GuardedBy("ImfLock.class") int getSequenceNumber() { return mCurSeq; } @@ -221,6 +229,7 @@ final class InputMethodBindingController { * Increase the current binding sequence number by one. * Reset to 1 on overflow. */ + @GuardedBy("ImfLock.class") void advanceSequenceNumber() { mCurSeq += 1; if (mCurSeq <= 0) { @@ -232,6 +241,7 @@ final class InputMethodBindingController { * If non-null, this is the input method service we are currently connected * to. */ + @GuardedBy("ImfLock.class") @Nullable IInputMethod getCurMethod() { return mCurMethod; @@ -240,6 +250,7 @@ final class InputMethodBindingController { /** * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}. */ + @GuardedBy("ImfLock.class") int getCurMethodUid() { return mCurMethodUid; } @@ -247,6 +258,7 @@ final class InputMethodBindingController { /** * Indicates whether {@link #mVisibleConnection} is currently in use. */ + @GuardedBy("ImfLock.class") boolean isVisibleBound() { return mVisibleBound; } @@ -254,11 +266,12 @@ final class InputMethodBindingController { /** * Used to bring IME service up to visible adjustment while it is being shown. */ + @GuardedBy("ImfLock.class") private final ServiceConnection mVisibleConnection = new ServiceConnection() { @Override public void onBindingDied(ComponentName name) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (mVisibleBound) { - unbindVisibleConnectionLocked(); + unbindVisibleConnection(); } } } @@ -273,17 +286,18 @@ final class InputMethodBindingController { /** * Used to bind the IME while it is not currently being shown. */ + @GuardedBy("ImfLock.class") private final ServiceConnection mMainConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected"); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { mCurMethod = IInputMethod.Stub.asInterface(service); - updateCurrentMethodUidLocked(); + updateCurrentMethodUid(); if (mCurToken == null) { Slog.w(TAG, "Service connected without a token!"); - unbindCurrentMethodLocked(); + unbindCurrentMethod(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return; } @@ -304,8 +318,8 @@ final class InputMethodBindingController { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - @GuardedBy("mMethodMap") - private void updateCurrentMethodUidLocked() { + @GuardedBy("ImfLock.class") + private void updateCurrentMethodUid() { final String curMethodPackage = mCurIntent.getComponent().getPackageName(); final int curMethodUid = mPackageManagerInternal.getPackageUid( curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId()); @@ -330,7 +344,7 @@ final class InputMethodBindingController { // refreshed when this method is called back. Running // adb install -r <APK that implements the current IME> // would be a good way to trigger such a situation. - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (DEBUG) { Slog.v(TAG, "Service disconnected: " + name + " mCurIntent=" + mCurIntent); } @@ -339,7 +353,7 @@ final class InputMethodBindingController { // We consider this to be a new bind attempt, since the system // should now try to restart the service for us. mLastBindTime = SystemClock.uptimeMillis(); - clearCurMethodAndSessionsLocked(); + clearCurMethodAndSessions(); mService.clearInputShowRequestLocked(); mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); } @@ -347,35 +361,35 @@ final class InputMethodBindingController { } }; - @GuardedBy("mMethodMap") - void unbindCurrentMethodLocked() { + @GuardedBy("ImfLock.class") + void unbindCurrentMethod() { if (mVisibleBound) { - unbindVisibleConnectionLocked(); + unbindVisibleConnection(); } if (mHasConnection) { - unbindMainConnectionLocked(); + unbindMainConnection(); } if (mCurToken != null) { - removeCurrentTokenLocked(); + removeCurrentToken(); mService.resetSystemUiLocked(); } mCurId = null; - clearCurMethodAndSessionsLocked(); + clearCurMethodAndSessions(); } - @GuardedBy("mMethodMap") - private void clearCurMethodAndSessionsLocked() { + @GuardedBy("ImfLock.class") + private void clearCurMethodAndSessions() { mService.clearClientSessionsLocked(); mCurMethod = null; mCurMethodUid = Process.INVALID_UID; } - @GuardedBy("mMethodMap") - private void removeCurrentTokenLocked() { - int curTokenDisplayId = mService.getCurTokenDisplayId(); + @GuardedBy("ImfLock.class") + private void removeCurrentToken() { + int curTokenDisplayId = mService.getCurTokenDisplayIdLocked(); if (DEBUG) { Slog.v(TAG, @@ -386,9 +400,9 @@ final class InputMethodBindingController { mCurToken = null; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @NonNull - InputBindResult bindCurrentMethodLocked() { + InputBindResult bindCurrentMethod() { InputMethodInfo info = mMethodMap.get(mSelectedMethodId); if (info == null) { throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId); @@ -396,11 +410,11 @@ final class InputMethodBindingController { mCurIntent = createImeBindingIntent(info.getComponent()); - if (bindCurrentInputMethodServiceMainConnectionLocked()) { + if (bindCurrentInputMethodServiceMainConnection()) { mCurId = info.getId(); mLastBindTime = SystemClock.uptimeMillis(); - addFreshWindowTokenLocked(); + addFreshWindowToken(); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, null, null, mCurId, mCurSeq, false); @@ -424,12 +438,12 @@ final class InputMethodBindingController { return intent; } - @GuardedBy("mMethodMap") - private void addFreshWindowTokenLocked() { - int displayIdToShowIme = mService.getDisplayIdToShowIme(); + @GuardedBy("ImfLock.class") + private void addFreshWindowToken() { + int displayIdToShowIme = mService.getDisplayIdToShowImeLocked(); mCurToken = new Binder(); - mService.setCurTokenDisplayId(displayIdToShowIme); + mService.setCurTokenDisplayIdLocked(displayIdToShowIme); try { if (DEBUG) { @@ -444,20 +458,20 @@ final class InputMethodBindingController { } } - @GuardedBy("mMethodMap") - private void unbindMainConnectionLocked() { + @GuardedBy("ImfLock.class") + private void unbindMainConnection() { mContext.unbindService(mMainConnection); mHasConnection = false; } - @GuardedBy("mMethodMap") - void unbindVisibleConnectionLocked() { + @GuardedBy("ImfLock.class") + void unbindVisibleConnection() { mContext.unbindService(mVisibleConnection); mVisibleBound = false; } - @GuardedBy("mMethodMap") - private boolean bindCurrentInputMethodServiceLocked(ServiceConnection conn, int flags) { + @GuardedBy("ImfLock.class") + private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) { if (mCurIntent == null || conn == null) { Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn); return false; @@ -466,16 +480,16 @@ final class InputMethodBindingController { new UserHandle(mSettings.getCurrentUserId())); } - @GuardedBy("mMethodMap") - private boolean bindCurrentInputMethodServiceVisibleConnectionLocked() { - mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection, + @GuardedBy("ImfLock.class") + private boolean bindCurrentInputMethodServiceVisibleConnection() { + mVisibleBound = bindCurrentInputMethodService(mVisibleConnection, IME_VISIBLE_BIND_FLAGS); return mVisibleBound; } - @GuardedBy("mMethodMap") - private boolean bindCurrentInputMethodServiceMainConnectionLocked() { - mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection, + @GuardedBy("ImfLock.class") + private boolean bindCurrentInputMethodServiceMainConnection() { + mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags); return mHasConnection; } @@ -486,27 +500,36 @@ final class InputMethodBindingController { * <p> * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds. */ - @GuardedBy("mMethodMap") - void setCurrentMethodVisibleLocked() { + @GuardedBy("ImfLock.class") + void setCurrentMethodVisible() { if (mCurMethod != null) { - if (DEBUG) Slog.d(TAG, "setCurrentMethodVisibleLocked: mCurToken=" + mCurToken); + if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken); if (mHasConnection && !mVisibleBound) { - bindCurrentInputMethodServiceVisibleConnectionLocked(); + bindCurrentInputMethodServiceVisibleConnection(); } return; } + // No IME is currently connected. Reestablish the main connection. + if (!mHasConnection) { + if (DEBUG) { + Slog.d(TAG, "Cannot show input: no IME bound. Rebinding."); + } + bindCurrentMethod(); + return; + } + long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime; - if (mHasConnection && bindingDuration >= TIME_TO_RECONNECT) { + if (bindingDuration >= TIME_TO_RECONNECT) { // The client has asked to have the input method shown, but // we have been sitting here too long with a connection to the // service and no interface received, so let's disconnect/connect // to try to prod things along. EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(), bindingDuration, 1); - Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisibleLocked()"); - unbindMainConnectionLocked(); - bindCurrentInputMethodServiceMainConnectionLocked(); + Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisible()"); + unbindMainConnection(); + bindCurrentInputMethodServiceMainConnection(); } else { if (DEBUG) { Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = " @@ -518,10 +541,10 @@ final class InputMethodBindingController { /** * Remove the binding needed for the IME to be shown. */ - @GuardedBy("mMethodMap") - void setCurrentMethodNotVisibleLocked() { + @GuardedBy("ImfLock.class") + void setCurrentMethodNotVisible() { if (mVisibleBound) { - unbindVisibleConnectionLocked(); + unbindVisibleConnection(); } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index da3729d7498f..c87dc8987b25 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -149,6 +149,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; +import com.android.internal.inputmethod.DirectBootAwareness; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.ImeTracing; @@ -261,6 +262,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final String ACTION_SHOW_INPUT_METHOD_PICKER = "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER"; + /** + * When set, {@link #startInputUncheckedLocked} will return + * {@link InputBindResult#NO_EDITOR} instead of starting an IME connection + * unless {@link StartInputFlags#IS_TEXT_EDITOR} is set. This behavior overrides + * {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE SOFT_INPUT_STATE_VISIBLE} and + * {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE SOFT_INPUT_STATE_ALWAYS_VISIBLE} + * starting from {@link android.os.Build.VERSION_CODES#P}. + */ + private final boolean mPreventImeStartupUnlessTextEditor; + @UserIdInt private int mLastSwitchUserId; @@ -292,9 +303,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Nullable private AudioManagerInternal mAudioManagerInternal = null; - - // All known input methods. mMethodMap also serves as the global - // lock for this class. + // All known input methods. final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>(); final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>(); final InputMethodSubtypeSwitchingController mSwitchingController; @@ -302,18 +311,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub /** * Tracks how many times {@link #mMethodMap} was updated. */ - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private int mMethodMapUpdateCount = 0; /** * The display id for which the latest startInput was called. */ - @GuardedBy("mMethodMap") - int getDisplayIdToShowIme() { + @GuardedBy("ImfLock.class") + int getDisplayIdToShowImeLocked() { return mDisplayIdToShowIme; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private int mDisplayIdToShowIme = INVALID_DISPLAY; // Ongoing notification @@ -399,7 +408,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>(); /** @@ -415,17 +424,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * <p>This can be transiently {@code null} when the system is re-initializing input method * settings, e.g., the system locale is just changed.</p> * - * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME is - * being connected to {@link InputMethodManagerService}.</p> + * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME + * is being connected to {@link InputMethodManagerService}.</p> * * @see InputMethodBindingController#getCurId() */ + @GuardedBy("ImfLock.class") @Nullable - private String getSelectedMethodId() { + String getSelectedMethodIdLocked() { return mBindingController.getSelectedMethodId(); } - private void setSelectedMethodId(@Nullable String selectedMethodId) { + @GuardedBy("ImfLock.class") + private void setSelectedMethodIdLocked(@Nullable String selectedMethodId) { mBindingController.setSelectedMethodId(selectedMethodId); } @@ -433,7 +444,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * The current binding sequence number, incremented every time there is * a new bind performed. */ - private int getSequenceNumber() { + @GuardedBy("ImfLock.class") + private int getSequenceNumberLocked() { return mBindingController.getSequenceNumber(); } @@ -441,7 +453,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * Increase the current binding sequence number by one. * Reset to 1 on overflow. */ - private void advanceSequenceNumber() { + @GuardedBy("ImfLock.class") + private void advanceSequenceNumberLocked() { mBindingController.advanceSequenceNumber(); } @@ -500,10 +513,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * * <p>This can be {@code null} when no input method is connected.</p> * - * @see #getSelectedMethodId() + * @see #getSelectedMethodIdLocked() */ + @GuardedBy("ImfLock.class") @Nullable - private String getCurId() { + private String getCurIdLocked() { return mBindingController.getCurId(); } @@ -521,7 +535,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). */ - private boolean hasConnection() { + @GuardedBy("ImfLock.class") + private boolean hasConnectionLocked() { return mBindingController.hasConnection(); } @@ -553,8 +568,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub /** * The Intent used to connect to the current input method. */ + @GuardedBy("ImfLock.class") @Nullable - private Intent getCurIntent() { + private Intent getCurIntentLocked() { return mBindingController.getCurIntent(); } @@ -562,27 +578,31 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * The token we have made for the currently active input method, to * identify it in the future. */ - private IBinder getCurToken() { + @GuardedBy("ImfLock.class") + private IBinder getCurTokenLocked() { return mBindingController.getCurToken(); } /** * The displayId of current active input method. */ - int getCurTokenDisplayId() { + @GuardedBy("ImfLock.class") + int getCurTokenDisplayIdLocked() { return mCurTokenDisplayId; } - void setCurTokenDisplayId(int curTokenDisplayId) { + @GuardedBy("ImfLock.class") + void setCurTokenDisplayIdLocked(int curTokenDisplayId) { mCurTokenDisplayId = curTokenDisplayId; } - int mCurTokenDisplayId = INVALID_DISPLAY; + @GuardedBy("ImfLock.class") + private int mCurTokenDisplayId = INVALID_DISPLAY; /** * The host input token of the current active input method. */ - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @Nullable private IBinder mCurHostInputToken; @@ -598,15 +618,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * If non-null, this is the input method service we are currently connected * to. */ + @GuardedBy("ImfLock.class") @Nullable - private IInputMethod getCurMethod() { + private IInputMethod getCurMethodLocked() { return mBindingController.getCurMethod(); } /** - * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}. + * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntentLocked()}. */ - private int getCurMethodUid() { + @GuardedBy("ImfLock.class") + private int getCurMethodUidLocked() { return mBindingController.getCurMethodUid(); } @@ -614,7 +636,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * Time that we last initiated a bind to the input method, to determine * if we should try to disconnect and reconnect to it. */ - private long getLastBindTime() { + @GuardedBy("ImfLock.class") + private long getLastBindTimeLocked() { return mBindingController.getLastBindTime(); } @@ -657,7 +680,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * </dd> * </dl> * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and - * {@link InputMethodBindingController#unbindCurrentMethodLocked()}.</em> + * {@link InputMethodBindingController#unbindCurrentMethod()}.</em> */ int mImeWindowVis; @@ -740,7 +763,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>(); private static final class SoftInputShowHideHistory { @@ -848,7 +871,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * {@link InputMethodManager#showSoftInput(View, int)}. * This map tracks origin of showSoftInput requests. */ - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private final WeakHashMap<IBinder, IBinder> mShowRequestWindowMap = new WeakHashMap<>(); /** @@ -856,7 +879,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}. * This map tracks origin of hideSoftInput requests. */ - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private final WeakHashMap<IBinder, IBinder> mHideRequestWindowMap = new WeakHashMap<>(); /** @@ -1013,11 +1036,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @NonNull private final StartInputHistory mStartInputHistory = new StartInputHistory(); - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @NonNull private final SoftInputShowHideHistory mSoftInputShowHideHistory = new SoftInputShowHideHistory(); @@ -1035,7 +1058,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub super(handler); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") public void registerContentObserverLocked(@UserIdInt int userId) { if (mRegistered && mUserId == userId) { return; @@ -1067,7 +1090,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (showImeUri.equals(uri)) { mMenuController.updateKeyboardFromSettingsLocked(); } else if (accessibilityRequestingNoImeUri.equals(uri)) { @@ -1173,7 +1196,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * <p>Caution: This method must not be called when system is not ready.</p> */ void onActionLocaleChanged() { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales(); if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) { return; @@ -1195,7 +1218,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * dynamically unless the entire package is updated, which also always triggers package * rescanning.</p> */ - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") final private ArraySet<String> mKnownImePackageNames = new ArraySet<>(); /** @@ -1218,17 +1241,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ private boolean mImePackageAppeared = false; - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void clearKnownImePackageNamesLocked() { mKnownImePackageNames.clear(); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") final void addKnownImePackageNameLocked(@NonNull String packageName) { mKnownImePackageNames.add(packageName); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean isChangingPackagesOfCurrentUserLocked() { final int userId = getChangingUserId(); final boolean retval = userId == mSettings.getCurrentUserId(); @@ -1242,7 +1265,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!isChangingPackagesOfCurrentUserLocked()) { return false; } @@ -1330,7 +1353,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mImePackageAppeared = false; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean shouldRebuildInputMethodListLocked() { // This method is guaranteed to be called only by getRegisteredHandler(). @@ -1354,7 +1377,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private void onFinishPackageChangesInternal() { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!isChangingPackagesOfCurrentUserLocked()) { return; } @@ -1477,7 +1500,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void run() { - synchronized (mService.mMethodMap) { + synchronized (ImfLock.class) { if (mService.mUserSwitchHandlerTask != this) { // This task was already canceled before it is handled here. So do nothing. return; @@ -1494,7 +1517,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * a handler callback. This needs to be set and unset only within the lock. */ @Nullable - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private UserSwitchHandlerTask mUserSwitchHandlerTask; public static final class Lifecycle extends SystemService { @@ -1516,7 +1539,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { // Called on ActivityManager thread. - synchronized (mService.mMethodMap) { + synchronized (ImfLock.class) { mService.scheduleSwitchUserTaskLocked(to.getUserIdentifier(), /* clientToBeReset= */ null); } @@ -1542,7 +1565,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } void onUnlockUser(@UserIdInt int userId) { - synchronized(mMethodMap) { + synchronized (ImfLock.class) { final int currentUserId = mSettings.getCurrentUserId(); if (DEBUG) { Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId); @@ -1559,7 +1582,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void scheduleSwitchUserTaskLocked(@UserIdInt int userId, @Nullable IInputMethodClient clientToBeReset) { if (mUserSwitchHandlerTask != null) { @@ -1648,12 +1671,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSettings, context); mMenuController = new InputMethodMenuController(this); mBindingController = new InputMethodBindingController(this); + mPreventImeStartupUnlessTextEditor = mRes.getBoolean( + com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void resetDefaultImeLocked(Context context) { // Do not reset the default (current) IME when it is a 3rd-party IME - String selectedMethodId = getSelectedMethodId(); + String selectedMethodId = getSelectedMethodIdLocked(); if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) { return; } @@ -1670,7 +1695,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void switchUserOnHandlerLocked(@UserIdInt int newUserId, IInputMethodClient clientToBeReset) { if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId @@ -1756,7 +1781,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } public void systemRunning(StatusBarManagerService statusBar) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (DEBUG) { Slog.d(TAG, "--- systemReady"); } @@ -1812,7 +1837,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Check whether or not this is a valid IPC. Assumes an IPC is valid when either // 1) it comes from the system process // 2) the calling process' user id is identical to the current user id IMMS thinks. - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean calledFromValidUserLocked() { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(uid); @@ -1856,12 +1881,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * @param token The window token given to the input method when it was started. * @return true if and only if non-null valid token is specified. */ - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean calledWithValidTokenLocked(@NonNull IBinder token) { if (token == null) { throw new InvalidParameterException("token must not be null."); } - if (token != getCurToken()) { + if (token != getCurTokenLocked()) { Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token." + " uid:" + Binder.getCallingUid() + " token:" + token); return false; @@ -1869,13 +1894,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return true; } - @Override - public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId) { + private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId, + @DirectBootAwareness int directBootAwareness) { if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, mSettings.getCurrentUserId(), null); if (resolvedUserIds.length != 1) { @@ -1883,7 +1908,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final long ident = Binder.clearCallingIdentity(); try { - return getInputMethodListLocked(resolvedUserIds[0]); + return getInputMethodListLocked(resolvedUserIds[0], directBootAwareness); } finally { Binder.restoreCallingIdentity(ident); } @@ -1891,12 +1916,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override + public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId) { + return getInputMethodListInternal(userId, DirectBootAwareness.AUTO); + } + + @Override + public List<InputMethodInfo> getAwareLockedInputMethodList(@UserIdInt int userId, + @DirectBootAwareness int directBootAwareness) { + return getInputMethodListInternal(userId, directBootAwareness); + } + + @Override public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, mSettings.getCurrentUserId(), null); if (resolvedUserIds.length != 1) { @@ -1911,10 +1947,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") - private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) { + @GuardedBy("ImfLock.class") + private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId, + @DirectBootAwareness int directBootAwareness) { final ArrayList<InputMethodInfo> methodList; - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getCurrentUserId() + && directBootAwareness == DirectBootAwareness.AUTO) { // Create a copy. methodList = new ArrayList<>(mMethodList); } else { @@ -1924,12 +1962,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList); + methodList, directBootAwareness); } return methodList; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) { if (userId == mSettings.getCurrentUserId()) { return mSettings.getEnabledInputMethodListLocked(); @@ -1940,18 +1978,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return settings.getEnabledInputMethodListLocked(); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) { - final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId()); + final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked()); try { - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); if (userId == mSettings.getCurrentUserId() && imi != null && imi.isInlineSuggestionsEnabled() && curMethod != null) { executeOrSendMessage(curMethod, mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, curMethod, requestInfo, new InlineSuggestionsRequestCallbackDecorator(callback, - imi.getPackageName(), mCurTokenDisplayId, getCurToken(), + imi.getPackageName(), mCurTokenDisplayId, + getCurTokenLocked(), this))); } else { callback.onInlineSuggestionsUnsupported(); @@ -2047,7 +2086,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * @param hostInputToken the host input token of the current active input method */ void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(callerImeToken)) { return; } @@ -2066,7 +2105,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlySelectedSubtypes) { final int callingUserId = UserHandle.getCallingUserId(); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, mSettings.getCurrentUserId(), null); if (resolvedUserIds.length != 1) { @@ -2082,12 +2121,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId, boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) { if (userId == mSettings.getCurrentUserId()) { final InputMethodInfo imi; - String selectedMethodId = getSelectedMethodId(); + String selectedMethodId = getSelectedMethodIdLocked(); if (imiId == null && selectedMethodId != null) { imi = mMethodMap.get(selectedMethodId); } else { @@ -2137,7 +2176,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // actually running. final int callerUid = Binder.getCallingUid(); final int callerPid = Binder.getCallingPid(); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { // TODO: Optimize this linear search. final int numClients = mClients.size(); for (int i = 0; i < numClients; ++i) { @@ -2170,7 +2209,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } void removeClient(IInputMethodClient client) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { ClientState cs = mClients.remove(client.asBinder()); if (cs != null) { client.asBinder().unlinkToDeath(cs.clientDeathRecipient, 0); @@ -2180,7 +2219,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); if (mBoundToMethod) { mBoundToMethod = false; - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null) { executeOrSendMessage(curMethod, mCaller.obtainMessageO( MSG_UNBIND_INPUT, curMethod)); @@ -2204,14 +2243,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) { if (mCurClient != null) { if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client=" + mCurClient.client.asBinder()); if (mBoundToMethod) { mBoundToMethod = false; - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null) { executeOrSendMessage(curMethod, mCaller.obtainMessageO( MSG_UNBIND_INPUT, curMethod)); @@ -2221,7 +2260,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */, false /* reportToImeController */); executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO( - MSG_UNBIND_CLIENT, getSequenceNumber(), unbindClientReason, mCurClient.client)); + MSG_UNBIND_CLIENT, getSequenceNumberLocked(), unbindClientReason, + mCurClient.client)); mCurClient.sessionRequested = false; mCurClient = null; @@ -2229,13 +2269,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void clearInputShowRequestLocked() { mShowRequested = mInputShown; mInputShown = false; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private int getImeShowFlagsLocked() { int flags = 0; if (mShowForced) { @@ -2247,7 +2287,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return flags; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private int getAppShowFlagsLocked() { int flags = 0; if (mShowForced) { @@ -2258,22 +2298,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return flags; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @NonNull InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { if (!mBoundToMethod) { - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); executeOrSendMessage(curMethod, mCaller.obtainMessageOO( MSG_BIND_INPUT, curMethod, mCurClient.binding)); mBoundToMethod = true; } final Binder startInputToken = new Binder(); - final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), getCurToken(), - mCurTokenDisplayId, getCurId(), startInputReason, !initial, + final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), + getCurTokenLocked(), + mCurTokenDisplayId, getCurIdLocked(), startInputReason, !initial, UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode, - getSequenceNumber()); + getSequenceNumberLocked()); mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow); mStartInputHistory.addEntry(info); @@ -2284,7 +2325,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.uid)) { mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(), - null /* intent */, UserHandle.getAppId(getCurMethodUid()), mCurClient.uid, + null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()), mCurClient.uid, true /* direct */); } @@ -2298,22 +2339,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub SoftInputShowHideReason.ATTACH_NEW_INPUT); } - String curId = getCurId(); + String curId = getCurIdLocked(); final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, session.session, (session.channel != null ? session.channel.dup() : null), - curId, getSequenceNumber(), suppressesSpellChecker); + curId, getSequenceNumberLocked(), suppressesSpellChecker); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @NonNull InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext, @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags, - @StartInputReason int startInputReason) { + @StartInputReason int startInputReason, int unverifiedTargetSdkVersion) { // If no method is currently selected, do nothing. - String selectedMethodId = getSelectedMethodId(); + String selectedMethodId = getSelectedMethodIdLocked(); if (selectedMethodId == null) { return InputBindResult.NO_IME; } @@ -2323,7 +2364,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // party code. return new InputBindResult( InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, - null, null, selectedMethodId, getSequenceNumber(), false); + null, null, selectedMethodId, getSequenceNumberLocked(), false); } if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid, @@ -2355,15 +2396,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // Bump up the sequence for this client and attach it. - advanceSequenceNumber(); + advanceSequenceNumberLocked(); mCurClient = cs; mCurInputContext = inputContext; mCurAttribute = attribute; + // If configured, we want to avoid starting up the IME if it is not supposed to be showing + if (mPreventImeStartupUnlessTextEditor + && !InputMethodUtils.isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, + startInputFlags) + && !mShowRequested) { + if (DEBUG) { + Slog.d(TAG, "Avoiding IME startup and unbinding current input method."); + } + mBindingController.unbindCurrentMethod(); + return InputBindResult.NO_EDITOR; + } + // Check if the input method is changing. // We expect the caller has already verified that the client is allowed to access this // display ID. - if (isSelectedMethodBound()) { + if (isSelectedMethodBoundLocked()) { if (cs.curSession != null) { // Fast case: if we are already connected to the input method, // then just return it. @@ -2377,18 +2430,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - mBindingController.unbindCurrentMethodLocked(); + mBindingController.unbindCurrentMethod(); - return mBindingController.bindCurrentMethodLocked(); + return mBindingController.bindCurrentMethod(); } - private boolean isSelectedMethodBound() { - String curId = getCurId(); - return curId != null && curId.equals(getSelectedMethodId()) + @GuardedBy("ImfLock.class") + private boolean isSelectedMethodBoundLocked() { + String curId = getCurIdLocked(); + return curId != null && curId.equals(getSelectedMethodIdLocked()) && mDisplayIdToShowIme == mCurTokenDisplayId; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void prepareClientSwitchLocked(ClientState cs) { // If the client is changing, we need to switch over to the new // one. @@ -2400,19 +2454,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @Nullable private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) { - if (hasConnection()) { - if (getCurMethod() != null) { + if (hasConnectionLocked()) { + if (getCurMethodLocked() != null) { // Return to client, and we will get back with it when // we have had a session made for it. requestClientSessionLocked(cs); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, - null, null, getCurId(), getSequenceNumber(), false); + null, null, getCurIdLocked(), getSequenceNumberLocked(), false); } else { - long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime(); + long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked(); if (bindingDuration < TIME_TO_RECONNECT) { // In this case we have connected to the service, but // don't yet have its interface. If it hasn't been too @@ -2423,10 +2477,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // to see if we can get back in touch with the service. return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, - null, null, getCurId(), getSequenceNumber(), false); + null, null, getCurIdLocked(), getSequenceNumberLocked(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, - getSelectedMethodId(), bindingDuration, 0); + getSelectedMethodIdLocked(), bindingDuration, 0); } } } @@ -2473,13 +2527,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (mUserSwitchHandlerTask != null) { // We have a pending user-switching task so it's better to just ignore this session. channel.dispose(); return; } - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null && method != null && curMethod.asBinder() == method.asBinder()) { if (mCurClient != null) { @@ -2501,7 +2555,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub channel.dispose(); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void resetSystemUiLocked() { // Set IME window status as invisible when unbinding current method. mImeWindowVis = 0; @@ -2511,14 +2565,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurHostInputToken = null; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) { - setSelectedMethodId(null); - mBindingController.unbindCurrentMethodLocked(); + setSelectedMethodIdLocked(null); + mBindingController.unbindCurrentMethod(); unbindCurrentClientLocked(unbindClientReason); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void reRequestCurrentClientSessionLocked() { if (mCurClient != null) { clearClientSessionLocked(mCurClient); @@ -2526,27 +2580,27 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void requestClientSessionLocked(ClientState cs) { if (!cs.sessionRequested) { if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); cs.sessionRequested = true; - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); executeOrSendMessage(curMethod, mCaller.obtainMessageOOO( MSG_CREATE_SESSION, curMethod, channels[1], new MethodCallback(this, curMethod, channels[0]))); } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void clearClientSessionLocked(ClientState cs) { finishSessionLocked(cs.curSession); cs.curSession = null; cs.sessionRequested = false; } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void finishSessionLocked(SessionState sessionState) { if (sessionState != null) { if (sessionState.session != null) { @@ -2565,9 +2619,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void clearClientSessionsLocked() { - if (getCurMethod() != null) { + if (getCurMethodLocked() != null) { final int numClients = mClients.size(); for (int i = 0; i < numClients; ++i) { clearClientSessionLocked(mClients.valueAt(i)); @@ -2584,7 +2638,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void updateStatusIcon(@NonNull IBinder token, String packageName, @DrawableRes int iconId) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -2618,14 +2672,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void hideStatusBarIconLocked() { if (mStatusBar != null) { mStatusBar.setIconVisibility(mSlotIme, false); } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean shouldShowImeSwitcherLocked(int visibility) { if (!mShowOngoingImeSwitcherForPhones) return false; if (mMenuController.getSwitchingDialogLocked() != null) return false; @@ -2694,7 +2748,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -2729,7 +2783,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void reportStartInput(@NonNull IBinder token, IBinder startInputToken) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -2742,7 +2796,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private void updateImeWindowStatus(boolean disableImeIcon) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (disableImeIcon) { updateSystemUiLocked(0, mBackDisposition); } else { @@ -2751,15 +2805,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void updateSystemUiLocked() { updateSystemUiLocked(mImeWindowVis, mBackDisposition); } // Caution! This method is called in this class. Handle multi-user carefully - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void updateSystemUiLocked(int vis, int backDisposition) { - if (getCurToken() == null) { + if (getCurTokenLocked() == null) { return; } if (DEBUG) { @@ -2784,10 +2838,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBar != null) { - mStatusBar.setImeWindowStatus(mCurTokenDisplayId, getCurToken(), vis, + mStatusBar.setImeWindowStatus(mCurTokenDisplayId, getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher); } - final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId()); + final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked()); if (imi != null && needsToShowImeSwitcher) { // Used to load label final CharSequence title = mRes.getText( @@ -2826,13 +2880,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void updateFromSettingsLocked(boolean enabledMayChange) { updateInputMethodsFromSettingsLocked(enabledMayChange); mMenuController.updateKeyboardFromSettingsLocked(); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { if (enabledMayChange) { List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); @@ -2887,7 +2941,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void setInputMethodLocked(String id, int subtypeId) { InputMethodInfo info = mMethodMap.get(id); if (info == null) { @@ -2895,7 +2949,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // See if we need to notify a subtype change within the same IME. - if (id.equals(getSelectedMethodId())) { + if (id.equals(getSelectedMethodIdLocked())) { final int subtypeCount = info.getSubtypeCount(); if (subtypeCount <= 0) { return; @@ -2916,7 +2970,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (newSubtype != oldSubtype) { setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null) { try { updateSystemUiLocked(mImeWindowVis, mBackDisposition); @@ -2938,7 +2992,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() // because mCurMethodId is stored as a history in // setSelectedInputMethodAndSubtypeLocked(). - setSelectedMethodId(id); + setSelectedMethodIdLocked(id); if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) { Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); @@ -2959,7 +3013,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#showSoftInput"); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledFromValidUserLocked()) { return false; } @@ -2995,7 +3049,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) { Objects.requireNonNull(windowToken, "windowToken must not be null"); int uid = Binder.getCallingUid(); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledFromValidUserLocked()) { return; } @@ -3012,7 +3066,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { mShowRequested = true; @@ -3031,12 +3085,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return false; } - mBindingController.setCurrentMethodVisibleLocked(); - if (getCurMethod() != null) { + mBindingController.setCurrentMethodVisible(); + if (getCurMethodLocked() != null) { // create a placeholder token for IMS so that IMS cannot inject windows into client app. Binder showInputToken = new Binder(); mShowRequestWindowMap.put(showInputToken, windowToken); - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT, getImeShowFlagsLocked(), reason, curMethod, resultReceiver, showInputToken)); @@ -3053,7 +3107,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#hideSoftInput"); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!InputMethodManagerService.this.calledFromValidUserLocked()) { return false; } @@ -3090,7 +3144,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 @@ -3111,7 +3165,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only // IMMS#InputShown indicates that the software keyboard is shown. // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested. - IInputMethod curMethod = getCurMethod(); + IInputMethod curMethod = getCurMethodLocked(); final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); boolean res; @@ -3128,7 +3182,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } else { res = false; } - mBindingController.setCurrentMethodNotVisibleLocked(); + mBindingController.setCurrentMethodNotVisible(); mInputShown = false; mShowRequested = false; mShowExplicitlyRequested = false; @@ -3184,7 +3238,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub userId = callingUserId; } final InputBindResult result; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final long ident = Binder.clearCallingIdentity(); try { result = startInputOrWindowGainedFocusInternalLocked(startInputReason, @@ -3209,7 +3263,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") @NonNull private InputBindResult startInputOrWindowGainedFocusInternalLocked( @StartInputReason int startInputReason, IInputMethodClient client, @@ -3302,7 +3356,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (attribute != null) { return startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags, - startInputReason); + startInputReason, unverifiedTargetSdkVersion); } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, @@ -3343,7 +3397,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (isTextEditor && attribute != null && shouldRestoreImeVisibility(windowToken, softInputMode)) { res = startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags, - startInputReason); + startInputReason, unverifiedTargetSdkVersion); showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); return res; @@ -3367,7 +3421,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. if (cs.selfReportedDisplayId != mCurTokenDisplayId) { - mBindingController.unbindCurrentMethodLocked(); + mBindingController.unbindCurrentMethod(); } } } else if (isTextEditor && doAutoShow @@ -3382,7 +3436,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, attribute, - startInputFlags, startInputReason); + startInputFlags, startInputReason, unverifiedTargetSdkVersion); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -3413,7 +3467,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub unverifiedTargetSdkVersion, startInputFlags)) { if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, attribute, - startInputFlags, startInputReason); + startInputFlags, startInputReason, unverifiedTargetSdkVersion); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -3432,7 +3486,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!sameWindowFocused) { if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, attribute, - startInputFlags, startInputReason); + startInputFlags, startInputReason, unverifiedTargetSdkVersion); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -3461,7 +3515,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } res = startInputUncheckedLocked(cs, inputContext, attribute, startInputFlags, - startInputReason); + startInputReason, unverifiedTargetSdkVersion); } else { res = InputBindResult.NULL_EDITOR_INFO; } @@ -3482,17 +3536,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mWindowManagerInternal.shouldRestoreImeVisibility(windowToken); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { // TODO(yukawa): multi-display support. final int uid = Binder.getCallingUid(); if (mCurFocusedWindowClient != null && client != null && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) { return true; - } else if (getCurIntent() != null && InputMethodUtils.checkIfPackageBelongsToUid( + } else if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid( mAppOpsManager, uid, - getCurIntent().getComponent().getPackageName())) { + getCurIntentLocked().getComponent().getPackageName())) { return true; } return false; @@ -3501,7 +3555,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledFromValidUserLocked()) { return; } @@ -3538,14 +3592,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * A test API for CTS to make sure that the input method menu is showing. */ public boolean isInputMethodPickerShownForTest() { - synchronized(mMethodMap) { + synchronized (ImfLock.class) { return mMenuController.isisInputMethodPickerShownForTestLocked(); } } @BinderThread private void setInputMethod(@NonNull IBinder token, String id) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -3556,7 +3610,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void setInputMethodAndSubtype(@NonNull IBinder token, String id, InputMethodSubtype subtype) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -3573,19 +3627,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { // TODO(yukawa): Should we verify the display ID? if (!calledFromValidUserLocked()) { return; } - executeOrSendMessage(getCurMethod(), mCaller.obtainMessageO( + executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageO( MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); } } @BinderThread private boolean switchToPreviousInputMethod(@NonNull IBinder token) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return false; } @@ -3599,7 +3653,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String targetLastImiId = null; int subtypeId = NOT_A_SUBTYPE_ID; if (lastIme != null && lastImi != null) { - final boolean imiIdIsSame = lastImi.getId().equals(getSelectedMethodId()); + final boolean imiIdIsSame = lastImi.getId().equals(getSelectedMethodIdLocked()); final int lastSubtypeHash = Integer.parseInt(lastIme.second); final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID : mCurrentSubtype.hashCode(); @@ -3645,7 +3699,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!TextUtils.isEmpty(targetLastImiId)) { if (DEBUG) { Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second - + ", from: " + getSelectedMethodId() + ", " + subtypeId); + + ", from: " + getSelectedMethodIdLocked() + ", " + subtypeId); } setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId); return true; @@ -3657,12 +3711,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return false; } final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - onlyCurrentIme, mMethodMap.get(getSelectedMethodId()), mCurrentSubtype); + onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype); if (nextSubtype == null) { return false; } @@ -3674,12 +3728,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return false; } final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodId()), + false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype); if (nextSubtype == null) { return false; @@ -3690,7 +3744,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputMethodSubtype getLastInputMethodSubtype() { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledFromValidUserLocked()) { return null; } @@ -3728,7 +3782,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + subtype.getLocale() + ", " + subtype.getMode()); } } - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledFromValidUserLocked()) { return; } @@ -3779,7 +3833,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public int getInputMethodWindowVisibleHeight() { // TODO(yukawa): Should we verify the display ID? - return mWindowManagerInternal.getInputMethodWindowVisibleHeight(mCurTokenDisplayId); + final int curTokenDisplayId; + synchronized (ImfLock.class) { + curTokenDisplayId = mCurTokenDisplayId; + } + return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId); } @Override @@ -3858,7 +3916,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void startImeTrace() { ImeTracing.getInstance().startTrace(null /* printwriter */); ArrayMap<IBinder, ClientState> clients; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { clients = new ArrayMap<>(mClients); } for (ClientState state : clients.values()) { @@ -3877,7 +3935,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void stopImeTrace() { ImeTracing.getInstance().stopTrace(null /* printwriter */); ArrayMap<IBinder, ClientState> clients; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { clients = new ArrayMap<>(mClients); } for (ClientState state : clients.values()) { @@ -3892,10 +3950,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private void dumpDebug(ProtoOutputStream proto, long fieldId) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final long token = proto.start(fieldId); - proto.write(CUR_METHOD_ID, getSelectedMethodId()); - proto.write(CUR_SEQ, getSequenceNumber()); + proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); + proto.write(CUR_SEQ, getSequenceNumberLocked()); proto.write(CUR_CLIENT, Objects.toString(mCurClient)); proto.write(CUR_FOCUSED_WINDOW_NAME, mWindowManagerInternal.getWindowName(mCurFocusedWindow)); @@ -3906,17 +3964,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mCurAttribute != null) { mCurAttribute.dumpDebug(proto, CUR_ATTRIBUTE); } - proto.write(CUR_ID, getCurId()); + proto.write(CUR_ID, getCurIdLocked()); proto.write(SHOW_REQUESTED, mShowRequested); proto.write(SHOW_EXPLICITLY_REQUESTED, mShowExplicitlyRequested); proto.write(SHOW_FORCED, mShowForced); proto.write(INPUT_SHOWN, mInputShown); proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode); - proto.write(CUR_TOKEN, Objects.toString(getCurToken())); + proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId); proto.write(SYSTEM_READY, mSystemReady); proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId); - proto.write(HAVE_CONNECTION, hasConnection()); + proto.write(HAVE_CONNECTION, hasConnectionLocked()); proto.write(BOUND_TO_METHOD, mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, mBackDisposition); @@ -3933,15 +3991,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) { Slog.d(TAG, "Got the notification of a user action."); } - synchronized (mMethodMap) { - if (getCurToken() != token) { + synchronized (ImfLock.class) { + if (getCurTokenLocked() != token) { if (DEBUG) { Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer" + " active."); } return; } - final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId()); + final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked()); if (imi != null) { mSwitchingController.onUserActionLocked(imi, mCurrentSubtype); } @@ -3951,7 +4009,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility"); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -3975,7 +4033,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -3985,7 +4043,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub "Using null token requires permission " + android.Manifest.permission.WRITE_SECURE_SETTINGS); } - } else if (getCurToken() != token) { + } else if (getCurTokenLocked() != token) { Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() + " token: " + token); return; @@ -4000,6 +4058,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** Called right after {@link IInputMethod#showSoftInput}. */ + @GuardedBy("ImfLock.class") private void onShowHideSoftInputRequested(boolean show, IBinder requestToken, @SoftInputShowHideReason int reason) { final WindowManagerInternal.ImeTargetInfo info = @@ -4014,7 +4073,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void hideMySoftInput(@NonNull IBinder token, int flags) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput"); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -4034,7 +4093,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void showMySoftInput(@NonNull IBinder token, int flags) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput"); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -4132,8 +4191,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final IBinder token = (IBinder) args.arg3; ((IInputMethod) args.arg1).showSoftInput( token, msg.arg1 /* flags */, (ResultReceiver) args.arg2); - final IBinder requestToken = mShowRequestWindowMap.get(token); - onShowHideSoftInputRequested(true /* show */, requestToken, reason); + final IBinder requestToken; + synchronized (ImfLock.class) { + requestToken = mShowRequestWindowMap.get(token); + onShowHideSoftInputRequested(true /* show */, requestToken, reason); + } } catch (RemoteException e) { } args.recycle(); @@ -4148,14 +4210,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final IBinder token = (IBinder) args.arg3; ((IInputMethod)args.arg1).hideSoftInput( token, 0 /* flags */, (ResultReceiver) args.arg2); - final IBinder requestToken = mHideRequestWindowMap.get(token); - onShowHideSoftInputRequested(false /* show */, requestToken, reason); + final IBinder requestToken; + synchronized (ImfLock.class) { + requestToken = mHideRequestWindowMap.get(token); + onShowHideSoftInputRequested(false /* show */, requestToken, reason); + } } catch (RemoteException e) { } args.recycle(); return true; case MSG_HIDE_CURRENT_INPUT_METHOD: - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final @SoftInputShowHideReason int reason = (int) msg.obj; hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason); @@ -4165,8 +4230,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub args = (SomeArgs)msg.obj; try { if (DEBUG) { - Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: " - + mCurTokenDisplayId); + synchronized (ImfLock.class) { + Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: " + + mCurTokenDisplayId); + } } final IBinder token = (IBinder) args.arg2; ((IInputMethod) args.arg1).initializeInternal(token, @@ -4193,7 +4260,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return true; } case MSG_REMOVE_IME_SURFACE: { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { try { if (mEnabledSession != null && mEnabledSession.session != null && !mShowRequested) { @@ -4206,7 +4273,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: { IBinder windowToken = (IBinder) msg.obj; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { try { if (windowToken == mCurFocusedWindow && mEnabledSession != null && mEnabledSession.session != null) { @@ -4348,7 +4415,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private void handleSetInteractive(final boolean interactive) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { mIsInteractive = interactive; updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition); @@ -4357,7 +4424,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean reportToImeController = false; try { reportToImeController = mPlatformCompat.isChangeEnabledByUid( - FINISH_INPUT_NO_FALLBACK_CONNECTION, getCurMethodUid()); + FINISH_INPUT_NO_FALLBACK_CONNECTION, getCurMethodUidLocked()); } catch (RemoteException e) { } scheduleSetActiveToClient(mCurClient, mIsInteractive, mInFullscreenMode, @@ -4372,7 +4439,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, 0, state)); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean chooseNewDefaultIMELocked() { final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME( mSettings.getEnabledInputMethodListLocked()); @@ -4389,17 +4456,31 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static void queryInputMethodServicesInternal(Context context, @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, - ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) { + ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, + @DirectBootAwareness int directBootAwareness) { methodList.clear(); methodMap.clear(); - // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default - // behavior of PackageManager is exactly what we want. It by default picks up appropriate - // services depending on the unlock state for the specified user. + final int directBootAwarenessFlags; + switch (directBootAwareness) { + case DirectBootAwareness.ANY: + directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + break; + case DirectBootAwareness.AUTO: + directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO; + break; + default: + directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO; + Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness + + ". Falling back to DirectBootAwareness.AUTO"); + break; + } + final int flags = PackageManager.GET_META_DATA + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + | directBootAwarenessFlags; final List<ResolveInfo> services = context.getPackageManager().queryIntentServicesAsUser( - new Intent(InputMethod.SERVICE_INTERFACE), - PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, - userId); + new Intent(InputMethod.SERVICE_INTERFACE), flags, userId); methodList.ensureCapacity(services.size()); methodMap.ensureCapacity(services.size()); @@ -4434,7 +4515,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") void buildInputMethodListLocked(boolean resetDefaultEnabledIme) { if (DEBUG) { Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme @@ -4448,7 +4529,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mMyPackageMonitor.clearKnownImePackageNamesLocked(); queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(), - mAdditionalSubtypeMap, mMethodMap, mMethodList); + mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO); // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is @@ -4542,7 +4623,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget(); } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void updateDefaultVoiceImeIfNeededLocked() { final String systemSpeechRecognizer = mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); @@ -4584,7 +4665,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); } final int userId; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { userId = mSettings.getCurrentUserId(); } mContext.startActivityAsUser(intent, null, UserHandle.of(userId)); @@ -4608,7 +4689,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * @param enabled {@code true} if {@code id} needs to be enabled. * @return {@code true} if the IME was previously enabled. {@code false} otherwise. */ - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean setInputMethodEnabledLocked(String id, boolean enabled) { List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings .getEnabledInputMethodsAndSubtypeListLocked(); @@ -4644,10 +4725,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly) { - mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodId(), mCurrentSubtype); + mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(), + mCurrentSubtype); // Set Subtype here if (imi == null || subtypeId < 0) { @@ -4671,7 +4753,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { InputMethodInfo imi = mMethodMap.get(newDefaultIme); int lastSubtypeId = NOT_A_SUBTYPE_ID; @@ -4695,7 +4777,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ @Override public InputMethodSubtype getCurrentInputMethodSubtype() { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { // TODO: Make this work even for non-current users? if (!calledFromValidUserLocked()) { return null; @@ -4704,9 +4786,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") InputMethodSubtype getCurrentInputMethodSubtypeLocked() { - String selectedMethodId = getSelectedMethodId(); + String selectedMethodId = getSelectedMethodIdLocked(); if (selectedMethodId == null) { return null; } @@ -4745,19 +4827,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mCurrentSubtype; } - @Nullable - String getCurrentMethodId() { - return getSelectedMethodId(); - } - private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { - synchronized (mMethodMap) { - return getInputMethodListLocked(userId); + synchronized (ImfLock.class) { + return getInputMethodListLocked(userId, DirectBootAwareness.AUTO); } } private List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { return getEnabledInputMethodListLocked(userId); } } @@ -4765,7 +4842,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { onCreateInlineSuggestionsRequestLocked(userId, requestInfo, callback); } } @@ -4777,12 +4854,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList); + methodMap, methodList, DirectBootAwareness.AUTO); return methodMap; } private boolean switchToInputMethod(String imeId, @UserIdInt int userId) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (userId == mSettings.getCurrentUserId()) { if (!mMethodMap.containsKey(imeId) || !mSettings.getEnabledInputMethodListLocked() @@ -4808,7 +4885,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (userId == mSettings.getCurrentUserId()) { if (!mMethodMap.containsKey(imeId)) { return false; // IME is not found. @@ -4840,7 +4917,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int displayId) { //TODO(b/150843766): Check if Input Token is valid. final IBinder curHostInputToken; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) { return false; } @@ -4850,7 +4927,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (mCurFocusedWindow != windowToken) { // mCurPerceptible was set by the focused window, but it is no longer in control, // so we reset mCurPerceptible. @@ -4958,13 +5035,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub throw new InvalidParameterException("contentUri must have content scheme"); } - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final int uid = Binder.getCallingUid(); - if (getSelectedMethodId() == null) { + if (getSelectedMethodIdLocked() == null) { return null; } - if (getCurToken() != token) { - Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurToken() + if (getCurTokenLocked() != token) { + Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurTokenLocked() + " token=" + token); return null; } @@ -4997,7 +5074,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { return; } @@ -5080,7 +5157,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final Printer p = new PrintWriterPrinter(pw); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { p.println("Current Input Method Manager state:"); int N = mMethodList.size(); p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount); @@ -5099,24 +5176,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" sessionRequested=" + ci.sessionRequested); p.println(" curSession=" + ci.curSession); } - p.println(" mCurMethodId=" + getSelectedMethodId()); + p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; - p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumber()); + p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); p.println(" mCurPerceptible=" + mCurPerceptible); p.println(" mCurFocusedWindow=" + mCurFocusedWindow + " softInputMode=" + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode) + " client=" + mCurFocusedWindowClient); focusedWindowClient = mCurFocusedWindowClient; - p.println(" mCurId=" + getCurId() + " mHaveConnection=" + hasConnection() + p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + mBindingController.isVisibleBound()); - p.println(" mCurToken=" + getCurToken()); + p.println(" mCurToken=" + getCurTokenLocked()); p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId); p.println(" mCurHostInputToken=" + mCurHostInputToken); - p.println(" mCurIntent=" + getCurIntent()); - method = getCurMethod(); - p.println(" mCurMethod=" + getCurMethod()); + p.println(" mCurIntent=" + getCurIntentLocked()); + method = getCurMethodLocked(); + p.println(" mCurMethod=" + getCurMethodLocked()); p.println(" mEnabledSession=" + mEnabledSession); p.println(" mShowRequested=" + mShowRequested + " mShowExplicitlyRequested=" + mShowExplicitlyRequested @@ -5365,7 +5442,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @ShellCommandResult private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { shellCommand.getOutPrintWriter().println(mLastSwitchUserId); return ShellCommandResult.SUCCESS; } @@ -5400,13 +5477,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub break; } } - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final PrintWriter pr = shellCommand.getOutPrintWriter(); final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { final List<InputMethodInfo> methods = all - ? getInputMethodListLocked(userId) + ? getInputMethodListLocked(userId, DirectBootAwareness.AUTO) : getEnabledInputMethodListLocked(userId); if (userIds.length > 1) { pr.print("User #"); @@ -5442,7 +5519,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final PrintWriter out = shellCommand.getOutPrintWriter(); final PrintWriter error = shellCommand.getErrPrintWriter(); boolean hasFailed = false; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { @@ -5496,7 +5573,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * @return {@code false} if it fails to enable the IME. {@code false} otherwise. */ @BinderThread - @GuardedBy("mMethodMap") + @GuardedBy("ImfLock.class") private boolean handleShellCommandEnableDisableInputMethodInternalLocked( @UserIdInt int userId, String imeId, boolean enabled, PrintWriter out, PrintWriter error) { @@ -5566,7 +5643,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final PrintWriter out = shellCommand.getOutPrintWriter(); final PrintWriter error = shellCommand.getErrPrintWriter(); boolean hasFailed = false; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { @@ -5604,7 +5681,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) { final PrintWriter out = shellCommand.getOutPrintWriter(); final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { @@ -5616,17 +5693,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (userId == mSettings.getCurrentUserId()) { hideCurrentInputLocked(mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); - mBindingController.unbindCurrentMethodLocked(); + mBindingController.unbindCurrentMethod(); // Reset the current IME resetSelectedInputMethodAndSubtypeLocked(null); // Also reset the settings of the current IME mSettings.putSelectedInputMethod(null); // Disable all enabled IMEs. - mSettings.getEnabledInputMethodListLocked().forEach( - imi -> setInputMethodEnabledLocked(imi.getId(), false)); + for (InputMethodInfo inputMethodInfo : + mSettings.getEnabledInputMethodListLocked()) { + setInputMethodEnabledLocked(inputMethodInfo.getId(), false); + } // Re-enable with default enabled IMEs. - InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList).forEach( - imi -> setInputMethodEnabledLocked(imi.getId(), true)); + for (InputMethodInfo imi : + InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList)) { + setInputMethodEnabledLocked(imi.getId(), true); + } updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, mSettings.getEnabledInputMethodListLocked(), @@ -5641,7 +5722,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList); + methodMap, methodList, DirectBootAwareness.AUTO); final InputMethodSettings settings = new InputMethodSettings( mContext.getResources(), mContext.getContentResolver(), methodMap, userId, false); @@ -5678,7 +5759,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final PrintWriter pw = shellCommand.getOutPrintWriter(); switch (cmd) { case "start": - ImeTracing.getInstance().getInstance().startTrace(pw); + ImeTracing.getInstance().startTrace(pw); break; // proceed to the next step to update the IME client processes. case "stop": ImeTracing.getInstance().stopTrace(pw); @@ -5695,7 +5776,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled(); ArrayMap<IBinder, ClientState> clients; - synchronized (mMethodMap) { + synchronized (ImfLock.class) { clients = new ArrayMap<>(mClients); } for (ClientState state : clients.values()) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index f70ad0537413..132be7d11ab1 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -46,6 +46,7 @@ import android.widget.RadioButton; import android.widget.Switch; import android.widget.TextView; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; @@ -98,7 +99,7 @@ public class InputMethodMenuController { int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); - synchronized (mMethodMap) { + synchronized (ImfLock.class) { final List<ImeSubtypeListItem> imList = mSwitchingController .getSortedInputMethodAndSubtypeListForImeMenuLocked( showAuxSubtypes, isScreenLocked); @@ -112,7 +113,7 @@ public class InputMethodMenuController { final InputMethodSubtype currentSubtype = mService.getCurrentInputMethodSubtypeLocked(); if (currentSubtype != null) { - final String curMethodId = mService.getCurrentMethodId(); + final String curMethodId = mService.getSelectedMethodIdLocked(); final InputMethodInfo currentImi = mMethodMap.get(curMethodId); lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( currentImi, currentSubtype.hashCode()); @@ -175,7 +176,7 @@ public class InputMethodMenuController { final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext, com.android.internal.R.layout.input_method_switch_item, imList, checkedItem); final DialogInterface.OnClickListener choiceListener = (dialog, which) -> { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (mIms == null || mIms.length <= which || mSubtypeIds == null || mSubtypeIds.length <= which) { return; @@ -250,11 +251,12 @@ public class InputMethodMenuController { } void hideInputMethodMenu() { - synchronized (mMethodMap) { + synchronized (ImfLock.class) { hideInputMethodMenuLocked(); } } + @GuardedBy("ImfLock.class") void hideInputMethodMenuLocked() { if (DEBUG) Slog.v(TAG, "Hide switching menu"); @@ -299,7 +301,7 @@ public class InputMethodMenuController { if (DEBUG) { Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available); } - synchronized (mMethodMap) { + synchronized (ImfLock.class) { if (mSwitchingDialog != null && mSwitchingDialogTitleView != null && mSwitchingDialog.isShowing()) { mSwitchingDialogTitleView.findViewById( diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index c340a2b77874..f8894c64304d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -39,7 +39,7 @@ import java.util.Objects; * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes. * * <p>This class is designed to be used from and only from {@link InputMethodManagerService} by - * using {@link InputMethodManagerService#mMethodMap} as a global lock.</p> + * using {@link ImfLock ImfLock.class} as a global lock.</p> */ final class InputMethodSubtypeSwitchingController { private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName(); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index 0fd7cc18a11c..e40d86a55b77 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -331,15 +331,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mAppOpsManager = context.getSystemService(AppOpsManager.class); startMonitoringOpChanges(); - - HostEndpointInfo info = new HostEndpointInfo(); - info.hostEndpointId = (char) mHostEndPointId; - info.packageName = mPackage; - info.attributionTag = mAttributionTag; - info.type = (mUid == Process.SYSTEM_UID) - ? HostEndpointInfo.Type.TYPE_FRAMEWORK - : HostEndpointInfo.Type.TYPE_APP; - mContextHubProxy.onHostEndpointConnected(info); + sendHostEndpointConnectedEvent(); } /* package */ ContextHubClientBroker( @@ -556,6 +548,9 @@ public class ContextHubClientBroker extends IContextHubClient.Stub /* package */ void onHubReset() { invokeCallback(callback -> callback.onHubReset()); sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET)); + + // Re-send the host endpoint connected event as the Context Hub restarted. + sendHostEndpointConnectedEvent(); } /** @@ -895,6 +890,17 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } + private void sendHostEndpointConnectedEvent() { + HostEndpointInfo info = new HostEndpointInfo(); + info.hostEndpointId = (char) mHostEndPointId; + info.packageName = mPackage; + info.attributionTag = mAttributionTag; + info.type = (mUid == Process.SYSTEM_UID) + ? HostEndpointInfo.Type.TYPE_FRAMEWORK + : HostEndpointInfo.Type.TYPE_APP; + mContextHubProxy.onHostEndpointConnected(info); + } + /** * Dump debugging info as ClientBrokerProto * diff --git a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java index 47146c1a6d70..d08e5dc94206 100644 --- a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java @@ -21,26 +21,23 @@ import static java.lang.Integer.bitCount; import android.annotation.Nullable; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.reflect.Array; +import java.util.ArrayList; import java.util.Arrays; +import java.util.ConcurrentModificationException; import java.util.NoSuchElementException; import java.util.Objects; /** * An in-memory event log to support historical event information. The log is of a constant size, * and new events will overwrite old events as the log fills up. - * - * @param <T> log event type */ public class LocalEventLog<T> { - /** - * Consumer of log events for iterating over the log. - * - * @param <T> log event type - */ + /** Consumer of log events for iterating over the log. */ public interface LogConsumer<T> { /** Invoked with a time and a logEvent. */ void acceptLog(long time, T logEvent); @@ -48,12 +45,13 @@ public class LocalEventLog<T> { // masks for the entries field. 1 bit is used to indicate whether this is a filler event or not, // and 31 bits to store the time delta. - private static final int IS_FILLER_MASK = 0b10000000000000000000000000000000; + private static final int IS_FILLER_MASK = 0b10000000000000000000000000000000; private static final int TIME_DELTA_MASK = 0b01111111111111111111111111111111; private static final int IS_FILLER_OFFSET = countTrailingZeros(IS_FILLER_MASK); private static final int TIME_DELTA_OFFSET = countTrailingZeros(TIME_DELTA_MASK); + @VisibleForTesting static final int MAX_TIME_DELTA = (1 << bitCount(TIME_DELTA_MASK)) - 1; private static int countTrailingZeros(int i) { @@ -79,7 +77,7 @@ public class LocalEventLog<T> { return (entry & IS_FILLER_MASK) != 0; } - // circular buffer of log entries and events. each entry corrosponds to the log event at the + // circular buffer of log entries and events. each entry corresponds to the log event at the // same index. the log entry holds the filler status and time delta according to the bit masks // above, and the log event is the log event. @@ -103,6 +101,9 @@ public class LocalEventLog<T> { @GuardedBy("this") long mLastLogTime; + @GuardedBy("this") + long mModificationCount; + @SuppressWarnings("unchecked") public LocalEventLog(int size, Class<T> clazz) { Preconditions.checkArgument(size > 0); @@ -143,6 +144,7 @@ public class LocalEventLog<T> { if (isEmpty()) { mStartTime = time; mLastLogTime = mStartTime; + mModificationCount++; } addLogEventInternal(false, (int) delta, logEvent); @@ -156,6 +158,7 @@ public class LocalEventLog<T> { if (mLogSize == mEntries.length) { // if log is full, size will remain the same, but update the start time mStartTime += getTimeDelta(mEntries[startIndex()]); + mModificationCount++; } else { // otherwise add an item mLogSize++; @@ -170,11 +173,12 @@ public class LocalEventLog<T> { /** Clears the log of all entries. */ public synchronized void clear() { - // clear entries to allow gc + // clear entries to aid gc Arrays.fill(mLogEvents, null); mLogEndIndex = 0; mLogSize = 0; + mModificationCount++; mStartTime = -1; mLastLogTime = -1; @@ -186,7 +190,10 @@ public class LocalEventLog<T> { return mLogSize == 0; } - /** Iterates over the event log, passing each log string to the given consumer. */ + /** + * Iterates over the event log, passing each log event to the given consumer. Locks the log + * while executing so that {@link ConcurrentModificationException}s cannot occur. + */ public synchronized void iterate(LogConsumer<? super T> consumer) { LogIterator it = new LogIterator(); while (it.hasNext()) { @@ -195,15 +202,53 @@ public class LocalEventLog<T> { } } + /** + * Iterates over all the given event logs in time order, passing each log event to the given + * consumer. It is the caller's responsibility to ensure that {@link + * ConcurrentModificationException}s cannot occur, whether through locking or other means. + */ + @SafeVarargs + public static <T> void iterate(LogConsumer<? super T> consumer, LocalEventLog<T>... logs) { + ArrayList<LocalEventLog<T>.LogIterator> its = new ArrayList<>(logs.length); + for (LocalEventLog<T> log : logs) { + LocalEventLog<T>.LogIterator it = log.new LogIterator(); + if (it.hasNext()) { + its.add(it); + it.next(); + } + } + + while (true) { + LocalEventLog<T>.LogIterator next = null; + for (LocalEventLog<T>.LogIterator it : its) { + if (it != null && (next == null || it.getTime() < next.getTime())) { + next = it; + } + } + + if (next == null) { + return; + } + + consumer.acceptLog(next.getTime(), next.getLog()); + + if (next.hasNext()) { + next.next(); + } else { + its.remove(next); + } + } + } + // returns the index of the first element @GuardedBy("this") - private int startIndex() { + int startIndex() { return wrapIndex(mLogEndIndex - mLogSize); } // returns the index after this one @GuardedBy("this") - private int incrementIndex(int index) { + int incrementIndex(int index) { if (index == -1) { return startIndex(); } else if (index >= 0) { @@ -215,12 +260,15 @@ public class LocalEventLog<T> { // rolls over the given index if necessary @GuardedBy("this") - private int wrapIndex(int index) { + int wrapIndex(int index) { // java modulo will keep negative sign, we need to rollover return (index % mEntries.length + mEntries.length) % mEntries.length; } - private class LogIterator { + /** Iterator over log times and events. */ + protected final class LogIterator { + + private final long mModificationCount; private long mLogTime; private int mIndex; @@ -229,8 +277,10 @@ public class LocalEventLog<T> { private long mCurrentTime; private T mCurrentLogEvent; - LogIterator() { + public LogIterator() { synchronized (LocalEventLog.this) { + mModificationCount = LocalEventLog.this.mModificationCount; + mLogTime = mStartTime; mIndex = -1; mCount = -1; @@ -241,6 +291,7 @@ public class LocalEventLog<T> { public boolean hasNext() { synchronized (LocalEventLog.this) { + checkModifications(); return mCount < mLogSize; } } @@ -277,5 +328,12 @@ public class LocalEventLog<T> { } } while (mCount < mLogSize && isFiller(mEntries[mIndex])); } + + @GuardedBy("LocalEventLog.this") + private void checkModifications() { + if (mModificationCount != LocalEventLog.this.mModificationCount) { + throw new ConcurrentModificationException(); + } + } } } diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index 94953e0b72f1..45436e753191 100644 --- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -52,16 +52,28 @@ public class LocationEventLog extends LocalEventLog<Object> { if (D) { return 600; } else { + return 300; + } + } + + private static int getLocationsLogSize() { + if (D) { return 200; + } else { + return 100; } } @GuardedBy("mAggregateStats") private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats; - public LocationEventLog() { + @GuardedBy("this") + private final LocationsEventLog mLocationsLog; + + private LocationEventLog() { super(getLogSize(), Object.class); mAggregateStats = new ArrayMap<>(4); + mLocationsLog = new LocationsEventLog(getLocationsLogSize()); } /** Copies out all aggregated stats. */ @@ -95,39 +107,39 @@ public class LocationEventLog extends LocalEventLog<Object> { /** Logs a user switched event. */ public void logUserSwitched(int userIdFrom, int userIdTo) { - addLogEvent(new UserSwitchedEvent(userIdFrom, userIdTo)); + addLog(new UserSwitchedEvent(userIdFrom, userIdTo)); } /** Logs a location enabled/disabled event. */ public void logLocationEnabled(int userId, boolean enabled) { - addLogEvent(new LocationEnabledEvent(userId, enabled)); + addLog(new LocationEnabledEvent(userId, enabled)); } /** Logs a location enabled/disabled event. */ public void logAdasLocationEnabled(int userId, boolean enabled) { - addLogEvent(new LocationAdasEnabledEvent(userId, enabled)); + addLog(new LocationAdasEnabledEvent(userId, enabled)); } /** Logs a location provider enabled/disabled event. */ public void logProviderEnabled(String provider, int userId, boolean enabled) { - addLogEvent(new ProviderEnabledEvent(provider, userId, enabled)); + addLog(new ProviderEnabledEvent(provider, userId, enabled)); } /** Logs a location provider being replaced/unreplaced by a mock provider. */ public void logProviderMocked(String provider, boolean mocked) { - addLogEvent(new ProviderMockedEvent(provider, mocked)); + addLog(new ProviderMockedEvent(provider, mocked)); } /** Logs a new client registration for a location provider. */ public void logProviderClientRegistered(String provider, CallerIdentity identity, LocationRequest request) { - addLogEvent(new ProviderClientRegisterEvent(provider, true, identity, request)); + addLog(new ProviderClientRegisterEvent(provider, true, identity, request)); getAggregateStats(provider, identity).markRequestAdded(request.getIntervalMillis()); } /** Logs a client unregistration for a location provider. */ public void logProviderClientUnregistered(String provider, CallerIdentity identity) { - addLogEvent(new ProviderClientRegisterEvent(provider, false, identity, null)); + addLog(new ProviderClientRegisterEvent(provider, false, identity, null)); getAggregateStats(provider, identity).markRequestRemoved(); } @@ -144,7 +156,7 @@ public class LocationEventLog extends LocalEventLog<Object> { /** Logs a client for a location provider entering the foreground state. */ public void logProviderClientForeground(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientForegroundEvent(provider, true, identity)); + addLog(new ProviderClientForegroundEvent(provider, true, identity)); } getAggregateStats(provider, identity).markRequestForeground(); } @@ -152,7 +164,7 @@ public class LocationEventLog extends LocalEventLog<Object> { /** Logs a client for a location provider leaving the foreground state. */ public void logProviderClientBackground(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientForegroundEvent(provider, false, identity)); + addLog(new ProviderClientForegroundEvent(provider, false, identity)); } getAggregateStats(provider, identity).markRequestBackground(); } @@ -160,32 +172,34 @@ public class LocationEventLog extends LocalEventLog<Object> { /** Logs a client for a location provider entering the permitted state. */ public void logProviderClientPermitted(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientPermittedEvent(provider, true, identity)); + addLog(new ProviderClientPermittedEvent(provider, true, identity)); } } /** Logs a client for a location provider leaving the permitted state. */ public void logProviderClientUnpermitted(String provider, CallerIdentity identity) { if (D) { - addLogEvent(new ProviderClientPermittedEvent(provider, false, identity)); + addLog(new ProviderClientPermittedEvent(provider, false, identity)); } } /** Logs a change to the provider request for a location provider. */ public void logProviderUpdateRequest(String provider, ProviderRequest request) { - addLogEvent(new ProviderUpdateEvent(provider, request)); + addLog(new ProviderUpdateEvent(provider, request)); } /** Logs a new incoming location for a location provider. */ public void logProviderReceivedLocations(String provider, int numLocations) { - addLogEvent(new ProviderReceiveLocationEvent(provider, numLocations)); + synchronized (this) { + mLocationsLog.logProviderReceivedLocations(provider, numLocations); + } } /** Logs a location deliver for a client of a location provider. */ public void logProviderDeliveredLocations(String provider, int numLocations, CallerIdentity identity) { - if (D) { - addLogEvent(new ProviderDeliverLocationEvent(provider, numLocations, identity)); + synchronized (this) { + mLocationsLog.logProviderDeliveredLocations(provider, numLocations, identity); } getAggregateStats(provider, identity).markLocationDelivered(); } @@ -193,19 +207,24 @@ public class LocationEventLog extends LocalEventLog<Object> { /** Logs that a provider has entered or exited stationary throttling. */ public void logProviderStationaryThrottled(String provider, boolean throttled, ProviderRequest request) { - addLogEvent(new ProviderStationaryThrottledEvent(provider, throttled, request)); + addLog(new ProviderStationaryThrottledEvent(provider, throttled, request)); } /** Logs that the location power save mode has changed. */ public void logLocationPowerSaveMode( @LocationPowerSaveMode int locationPowerSaveMode) { - addLogEvent(new LocationPowerSaveModeEvent(locationPowerSaveMode)); + addLog(new LocationPowerSaveModeEvent(locationPowerSaveMode)); } - private void addLogEvent(Object logEvent) { + private void addLog(Object logEvent) { addLog(SystemClock.elapsedRealtime(), logEvent); } + @Override + public synchronized void iterate(LogConsumer<? super Object> consumer) { + iterate(consumer, this, mLocationsLog); + } + public void iterate(Consumer<String> consumer) { iterate(consumer, null); } @@ -488,6 +507,26 @@ public class LocationEventLog extends LocalEventLog<Object> { } } + private static final class LocationsEventLog extends LocalEventLog<Object> { + + LocationsEventLog(int size) { + super(size, Object.class); + } + + public void logProviderReceivedLocations(String provider, int numLocations) { + addLog(new ProviderReceiveLocationEvent(provider, numLocations)); + } + + public void logProviderDeliveredLocations(String provider, int numLocations, + CallerIdentity identity) { + addLog(new ProviderDeliverLocationEvent(provider, numLocations, identity)); + } + + private void addLog(Object logEvent) { + this.addLog(SystemClock.elapsedRealtime(), logEvent); + } + } + /** * Aggregate statistics for a single package under a single provider. */ diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 699f1439538e..7bb0d4899de5 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -114,7 +114,8 @@ public final class GnssMeasurementsProvider extends protected boolean registerWithService(GnssMeasurementRequest request, Collection<GnssListenerRegistration> registrations) { if (mGnssNative.startMeasurementCollection(request.isFullTracking(), - request.isCorrelationVectorOutputsEnabled())) { + request.isCorrelationVectorOutputsEnabled(), + request.getIntervalMillis())) { if (D) { Log.d(TAG, "starting gnss measurements (" + request + ")"); } diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index 1eef0de3a05d..a513e0898344 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -623,8 +623,38 @@ public class GnssNative { public void injectLocation(Location location) { Preconditions.checkState(mRegistered); if (location.hasAccuracy()) { - mGnssHal.injectLocation(location.getLatitude(), location.getLongitude(), - location.getAccuracy()); + + int gnssLocationFlags = GNSS_LOCATION_HAS_LAT_LONG + | (location.hasAltitude() ? GNSS_LOCATION_HAS_ALTITUDE : 0) + | (location.hasSpeed() ? GNSS_LOCATION_HAS_SPEED : 0) + | (location.hasBearing() ? GNSS_LOCATION_HAS_BEARING : 0) + | (location.hasAccuracy() ? GNSS_LOCATION_HAS_HORIZONTAL_ACCURACY : 0) + | (location.hasVerticalAccuracy() ? GNSS_LOCATION_HAS_VERTICAL_ACCURACY : 0) + | (location.hasSpeedAccuracy() ? GNSS_LOCATION_HAS_SPEED_ACCURACY : 0) + | (location.hasBearingAccuracy() ? GNSS_LOCATION_HAS_BEARING_ACCURACY : 0); + + double latitudeDegrees = location.getLatitude(); + double longitudeDegrees = location.getLongitude(); + double altitudeMeters = location.getAltitude(); + float speedMetersPerSec = location.getSpeed(); + float bearingDegrees = location.getBearing(); + float horizontalAccuracyMeters = location.getAccuracy(); + float verticalAccuracyMeters = location.getVerticalAccuracyMeters(); + float speedAccuracyMetersPerSecond = location.getSpeedAccuracyMetersPerSecond(); + float bearingAccuracyDegrees = location.getBearingAccuracyDegrees(); + long timestamp = location.getTime(); + + int elapsedRealtimeFlags = GNSS_REALTIME_HAS_TIMESTAMP_NS + | (location.hasElapsedRealtimeUncertaintyNanos() + ? GNSS_REALTIME_HAS_TIME_UNCERTAINTY_NS : 0); + long elapsedRealtimeNanos = location.getElapsedRealtimeNanos(); + double elapsedRealtimeUncertaintyNanos = location.getElapsedRealtimeUncertaintyNanos(); + + mGnssHal.injectLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees, + altitudeMeters, speedMetersPerSec, bearingDegrees, horizontalAccuracyMeters, + verticalAccuracyMeters, speedAccuracyMetersPerSecond, bearingAccuracyDegrees, + timestamp, elapsedRealtimeFlags, elapsedRealtimeNanos, + elapsedRealtimeUncertaintyNanos); } } @@ -735,9 +765,10 @@ public class GnssNative { * Starts measurement collection. */ public boolean startMeasurementCollection(boolean enableFullTracking, - boolean enableCorrVecOutputs) { + boolean enableCorrVecOutputs, int intervalMillis) { Preconditions.checkState(mRegistered); - return mGnssHal.startMeasurementCollection(enableFullTracking, enableCorrVecOutputs); + return mGnssHal.startMeasurementCollection(enableFullTracking, enableCorrVecOutputs, + intervalMillis); } /** @@ -1262,8 +1293,15 @@ public class GnssNative { return native_read_nmea(buffer, bufferSize); } - protected void injectLocation(double latitude, double longitude, float accuracy) { - native_inject_location(latitude, longitude, accuracy); + protected void injectLocation(@GnssLocationFlags int gnssLocationFlags, double latitude, + double longitude, double altitude, float speed, float bearing, + float horizontalAccuracy, float verticalAccuracy, float speedAccuracy, + float bearingAccuracy, long timestamp, @GnssRealtimeFlags int elapsedRealtimeFlags, + long elapsedRealtimeNanos, double elapsedRealtimeUncertaintyNanos) { + native_inject_location(gnssLocationFlags, latitude, longitude, altitude, speed, + bearing, horizontalAccuracy, verticalAccuracy, speedAccuracy, bearingAccuracy, + timestamp, elapsedRealtimeFlags, elapsedRealtimeNanos, + elapsedRealtimeUncertaintyNanos); } protected void injectBestLocation(@GnssLocationFlags int gnssLocationFlags, double latitude, @@ -1310,8 +1348,9 @@ public class GnssNative { } protected boolean startMeasurementCollection(boolean enableFullTracking, - boolean enableCorrVecOutputs) { - return native_start_measurement_collection(enableFullTracking, enableCorrVecOutputs); + boolean enableCorrVecOutputs, int intervalMillis) { + return native_start_measurement_collection(enableFullTracking, enableCorrVecOutputs, + intervalMillis); } protected boolean stopMeasurementCollection() { @@ -1436,8 +1475,13 @@ public class GnssNative { // location injection APIs - private static native void native_inject_location(double latitude, double longitude, - float accuracy); + private static native void native_inject_location( + int gnssLocationFlags, double latitudeDegrees, double longitudeDegrees, + double altitudeMeters, float speedMetersPerSec, float bearingDegrees, + float horizontalAccuracyMeters, float verticalAccuracyMeters, + float speedAccuracyMetersPerSecond, float bearingAccuracyDegrees, + long timestamp, int elapsedRealtimeFlags, long elapsedRealtimeNanos, + double elapsedRealtimeUncertaintyNanos); private static native void native_inject_best_location( @@ -1475,7 +1519,7 @@ public class GnssNative { private static native boolean native_is_measurement_supported(); private static native boolean native_start_measurement_collection(boolean enableFullTracking, - boolean enableCorrVecOutputs); + boolean enableCorrVecOutputs, int intervalMillis); private static native boolean native_stop_measurement_collection(); diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 771bf19761cf..1ba32ac1eec2 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -245,38 +245,19 @@ public class LocationProviderManager extends intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0])); } - PendingIntent.OnFinished onFinished = null; - - // send() SHOULD only run the completion callback if it completes successfully. however, - // b/201299281 (which could not be fixed in the S timeframe) means that it's possible - // for send() to throw an exception AND run the completion callback. if this happens, we - // would over-release the wakelock... we take matters into our own hands to ensure that - // the completion callback can only be run if send() completes successfully. this means - // the completion callback may be run inline - but as we've never specified what thread - // the callback is run on, this is fine. - GatedCallback gatedCallback; + Runnable callback = null; if (onCompleteCallback != null) { - gatedCallback = new GatedCallback(() -> { + callback = () -> { try { onCompleteCallback.sendResult(null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - }); - onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run(); - } else { - gatedCallback = new GatedCallback(null); + }; } - mPendingIntent.send( - mContext, - 0, - intent, - onFinished, - null, - null, + PendingIntentSender.send(mPendingIntent, mContext, intent, callback, options.toBundle()); - gatedCallback.allow(); } @Override @@ -1783,12 +1764,26 @@ public class LocationProviderManager extends ICancellationSignal cancelTransport = CancellationSignal.createTransport(); CancellationSignal.fromTransport(cancelTransport) - .setOnCancelListener(SingleUseCallback.wrap( + .setOnCancelListener( () -> { - synchronized (mLock) { - removeRegistration(callback.asBinder(), registration); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + removeRegistration(callback.asBinder(), registration); + } + } catch (RuntimeException e) { + // since this is within a oneway binder transaction there is nowhere + // for exceptions to go - move onto another thread to crash system + // server so we find out about it + FgThread.getExecutor().execute(() -> { + throw new AssertionError(e); + }); + throw e; + } finally { + Binder.restoreCallingIdentity(ident); } - })); + + }); return cancelTransport; } @@ -2733,103 +2728,84 @@ public class LocationProviderManager extends } } - private static class SingleUseCallback extends IRemoteCallback.Stub implements Runnable, - CancellationSignal.OnCancelListener { - - public static @Nullable SingleUseCallback wrap(@Nullable Runnable callback) { - return callback == null ? null : new SingleUseCallback(callback); - } - - @GuardedBy("this") - private @Nullable Runnable mCallback; + private static class PendingIntentSender { - private SingleUseCallback(Runnable callback) { - mCallback = Objects.requireNonNull(callback); - } - - @Override - public void sendResult(Bundle data) { - run(); - } - - @Override - public void onCancel() { - run(); - } - - @Override - public void run() { - Runnable callback; - synchronized (this) { - callback = mCallback; - mCallback = null; - } - - // prevent this callback from being run more than once - otherwise this could provide an - // attack vector for a malicious app to break assumptions on how many times a callback - // may be invoked, and thus crash system server. - if (callback == null) { - return; + // send() SHOULD only run the OnFinished callback if it completes successfully. however, + // b/201299281 (which could not be fixed in the S timeframe) means that it's possible + // for send() to throw an exception AND run the completion callback which breaks the + // guarantee we rely on. we take matters into our own hands to ensure that the OnFinished + // callback can only be run if send() completes successfully. this means the OnFinished + // callback may be run inline, so there is no longer any guarantee about what thread the + // callback will be run on. + public static void send(PendingIntent pendingIntent, Context context, Intent intent, + @Nullable final Runnable callback, Bundle options) + throws PendingIntent.CanceledException { + GatedCallback gatedCallback; + PendingIntent.OnFinished onFinished; + if (callback != null) { + gatedCallback = new GatedCallback(callback); + onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run(); + } else { + gatedCallback = null; + onFinished = null; } - final long identity = Binder.clearCallingIdentity(); - try { - callback.run(); - } catch (RuntimeException e) { - // since this is within a oneway binder transaction there is nowhere - // for exceptions to go - move onto another thread to crash system - // server so we find out about it - FgThread.getExecutor().execute(() -> { - throw new AssertionError(e); - }); - throw e; - } finally { - Binder.restoreCallingIdentity(identity); + pendingIntent.send( + context, + 0, + intent, + onFinished, + null, + null, + options); + if (gatedCallback != null) { + gatedCallback.allow(); } } - } - private static class GatedCallback implements Runnable { + private static class GatedCallback implements Runnable { - private @Nullable Runnable mCallback; + @GuardedBy("this") + private @Nullable Runnable mCallback; - @GuardedBy("this") - private boolean mGate; - @GuardedBy("this") - private boolean mRun; + @GuardedBy("this") + private boolean mGate; + @GuardedBy("this") + private boolean mRun; - GatedCallback(@Nullable Runnable callback) { - mCallback = callback; - } + private GatedCallback(@Nullable Runnable callback) { + mCallback = callback; + } - public void allow() { - Runnable callback = null; - synchronized (this) { - mGate = true; - if (mRun && mCallback != null) { - callback = mCallback; - mCallback = null; + public void allow() { + Runnable callback = null; + synchronized (this) { + mGate = true; + if (mRun && mCallback != null) { + callback = mCallback; + mCallback = null; + } } - } - if (callback != null) { - callback.run(); + if (callback != null) { + callback.run(); + } } - } - @Override - public void run() { - Runnable callback = null; - synchronized (this) { - mRun = true; - if (mGate && mCallback != null) { - callback = mCallback; - mCallback = null; + @Override + public void run() { + Runnable callback = null; + synchronized (this) { + mRun = true; + if (mGate && mCallback != null) { + callback = mCallback; + mCallback = null; + } } - } - if (callback != null) { - callback.run(); + if (callback != null) { + callback.run(); + } } } } @@ -2850,7 +2826,19 @@ public class LocationProviderManager extends try { mWakeLock.release(); } catch (RuntimeException e) { - Log.e(TAG, "wakelock over-released by " + mIdentity, e); + // wakelock throws a RuntimeException instead of some more specific exception, so + // attempt to capture only actual RuntimeExceptions + if (e.getClass() == RuntimeException.class) { + Log.e(TAG, "wakelock over-released by " + mIdentity, e); + } else { + // since this is within a oneway binder transaction there is nowhere for + // exceptions to go - move onto another thread to crash system server so we find + // out about it + FgThread.getExecutor().execute(() -> { + throw new AssertionError(e); + }); + throw e; + } } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 0a47cbbdc0fc..65c5b88d846a 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -2005,7 +2005,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (final NetworkStateSnapshot snapshot : snapshots) { mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot)); - // Policies matched by NPMS only match by subscriber ID or by ssid. Thus subtype + // Policies matched by NPMS only match by subscriber ID or by network ID. Thus subtype // in the object created here is never used and its value doesn't matter, so use // NETWORK_TYPE_UNKNOWN. final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, @@ -2435,7 +2435,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final NetworkTemplate.Builder builder = new NetworkTemplate.Builder(templateType) - .setWifiNetworkKey(networkId) .setMeteredness(templateMeteredness); if (subscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT) { @@ -2443,6 +2442,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { ids.add(subscriberId); builder.setSubscriberIds(ids); } + if (networkId != null) { + builder.setWifiNetworkKeys(Set.of(networkId)); + } final NetworkTemplate template = builder.build(); if (NetworkPolicy.isTemplatePersistable(template)) { mNetworkPolicy.put(template, new NetworkPolicy(template, cycleRule, @@ -2593,35 +2595,39 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * into {@link WifiConfiguration}. */ private void upgradeWifiMeteredOverride() { - final ArrayMap<String, Boolean> wifiNetworkIds = new ArrayMap<>(); + final ArrayMap<String, Boolean> wifiNetworkKeys = new ArrayMap<>(); synchronized (mNetworkPoliciesSecondLock) { for (int i = 0; i < mNetworkPolicy.size();) { final NetworkPolicy policy = mNetworkPolicy.valueAt(i); if (policy.template.getMatchRule() == NetworkTemplate.MATCH_WIFI && !policy.inferred) { mNetworkPolicy.removeAt(i); - wifiNetworkIds.put(policy.template.getNetworkId(), policy.metered); + final Set<String> keys = policy.template.getWifiNetworkKeys(); + wifiNetworkKeys.put(keys.isEmpty() ? null : keys.iterator().next(), + policy.metered); } else { i++; } } } - if (wifiNetworkIds.isEmpty()) { + if (wifiNetworkKeys.isEmpty()) { return; } final WifiManager wm = mContext.getSystemService(WifiManager.class); final List<WifiConfiguration> configs = wm.getConfiguredNetworks(); for (int i = 0; i < configs.size(); ++i) { final WifiConfiguration config = configs.get(i); - final String networkId = resolveNetworkId(config); - final Boolean metered = wifiNetworkIds.get(networkId); - if (metered != null) { - Slog.d(TAG, "Found network " + networkId + "; upgrading metered hint"); - config.meteredOverride = metered - ? WifiConfiguration.METERED_OVERRIDE_METERED - : WifiConfiguration.METERED_OVERRIDE_NOT_METERED; - wm.updateNetwork(config); + for (String key : config.getAllPersistableNetworkKeys()) { + final Boolean metered = wifiNetworkKeys.get(key); + if (metered != null) { + Slog.d(TAG, "Found network " + key + "; upgrading metered hint"); + config.meteredOverride = metered + ? WifiConfiguration.METERED_OVERRIDE_METERED + : WifiConfiguration.METERED_OVERRIDE_NOT_METERED; + wm.updateNetwork(config); + break; + } } } @@ -2663,9 +2669,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { ? NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL : NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT; writeIntAttribute(out, ATTR_SUBSCRIBER_ID_MATCH_RULE, subscriberIdMatchRule); - final String networkId = template.getNetworkId(); - if (networkId != null) { - out.attribute(null, ATTR_NETWORK_ID, networkId); + if (!template.getWifiNetworkKeys().isEmpty()) { + out.attribute(null, ATTR_NETWORK_ID, + template.getWifiNetworkKeys().iterator().next()); } writeIntAttribute(out, ATTR_TEMPLATE_METERED, template.getMeteredness()); diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index bedb8b9f7a18..22cd06dfd16b 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -101,6 +101,12 @@ final class AppDataHelper { mPm.mSettings.writeKernelMappingLPr(ps); } + // TODO(b/211761016): should we still create the profile dirs? + if (!shouldHaveAppStorage(pkg)) { + Slog.w(TAG, "Skipping preparing app data for " + pkg.getPackageName()); + return; + } + Installer.Batch batch = new Installer.Batch(); UserManagerInternal umInternal = mInjector.getUserManagerInternal(); StorageManagerInternal smInternal = mInjector.getLocalService( @@ -161,6 +167,10 @@ final class AppDataHelper { Slog.wtf(TAG, "Package was null!", new Throwable()); return CompletableFuture.completedFuture(null); } + if (!shouldHaveAppStorage(pkg)) { + Slog.w(TAG, "Skipping preparing app data for " + pkg.getPackageName()); + return CompletableFuture.completedFuture(null); + } return prepareAppDataLeaf(batch, pkg, previousAppId, userId, flags); } @@ -381,7 +391,7 @@ final class AppDataHelper { for (File file : files) { final String packageName = file.getName(); try { - assertPackageKnownAndInstalled(volumeUuid, packageName, userId); + assertPackageStorageValid(volumeUuid, packageName, userId); } catch (PackageManagerException e) { logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e); try { @@ -398,7 +408,7 @@ final class AppDataHelper { for (File file : files) { final String packageName = file.getName(); try { - assertPackageKnownAndInstalled(volumeUuid, packageName, userId); + assertPackageStorageValid(volumeUuid, packageName, userId); } catch (PackageManagerException e) { logCriticalInfo(Log.WARN, "Destroying " + file + " due to: " + e); try { @@ -446,7 +456,11 @@ final class AppDataHelper { return result; } - private void assertPackageKnownAndInstalled(String volumeUuid, String packageName, int userId) + /** + * Asserts that storage path is valid by checking that {@code packageName} is present, + * installed for the given {@code userId} and can have app data. + */ + private void assertPackageStorageValid(String volumeUuid, String packageName, int userId) throws PackageManagerException { synchronized (mPm.mLock) { // Normalize package name to handle renamed packages @@ -462,6 +476,13 @@ final class AppDataHelper { } else if (!ps.getInstalled(userId)) { throw new PackageManagerException( "Package " + packageName + " not installed for user " + userId); + } else if (ps.getPkg() == null) { + throw new PackageManagerException("Package " + packageName + " is not parsed yet"); + } else { + if (!shouldHaveAppStorage(ps.getPkg())) { + throw new PackageManagerException( + "Package " + packageName + " shouldn't have storage"); + } } } } @@ -603,4 +624,13 @@ final class AppDataHelper { Slog.w(TAG, String.valueOf(e)); } } + + /** + * Returns {@code true} if app's internal storage should be created for this {@code pkg}. + */ + private boolean shouldHaveAppStorage(AndroidPackage pkg) { + PackageManager.Property noAppDataProp = + pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); + return noAppDataProp == null || !noAppDataProp.getBoolean(); + } } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 2128beaa7322..2f4f2715f2b6 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -320,10 +320,7 @@ public class ComputerEngine implements Computer { private final WatchedArrayMap<String, AndroidPackage> mPackages; private final WatchedArrayMap<ComponentName, ParsedInstrumentation> mInstrumentation; - private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> - mStaticLibsByDeclaringPackage; - private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> - mSharedLibraries; + private final SharedLibrariesRead mSharedLibraries; private final ComponentName mLocalResolveComponentName; private final ActivityInfo mResolveActivity; private final WatchedSparseBooleanArray mWebInstantAppsDisabled; @@ -375,8 +372,7 @@ public class ComputerEngine implements Computer { mSettings = new Settings(args.settings); mIsolatedOwners = args.isolatedOwners; mPackages = args.packages; - mSharedLibraries = args.sharedLibs; - mStaticLibsByDeclaringPackage = args.staticLibs; + mSharedLibraries = args.sharedLibraries; mInstrumentation = args.instrumentation; mWebInstantAppsDisabled = args.webInstantAppsDisabled; mLocalResolveComponentName = args.resolveComponentName; @@ -1564,7 +1560,7 @@ public class ComputerEngine implements Computer { : mPermissionManager.getGrantedPermissions(ps.getPackageName(), userId); PackageInfo packageInfo = PackageInfoUtils.generate(p, gids, flags, - ps.getFirstInstallTime(), ps.getLastUpdateTime(), permissions, state, userId, + state.getFirstInstallTime(), ps.getLastUpdateTime(), permissions, state, userId, ps); if (packageInfo == null) { @@ -1581,7 +1577,7 @@ public class ComputerEngine implements Computer { pi.packageName = ps.getPackageName(); pi.setLongVersionCode(ps.getVersionCode()); pi.sharedUserId = (ps.getSharedUser() != null) ? ps.getSharedUser().name : null; - pi.firstInstallTime = ps.getFirstInstallTime(); + pi.firstInstallTime = state.getFirstInstallTime(); pi.lastUpdateTime = ps.getLastUpdateTime(); ApplicationInfo ai = new ApplicationInfo(); @@ -2006,8 +2002,7 @@ public class ComputerEngine implements Computer { @Nullable public final SharedLibraryInfo getSharedLibraryInfo(String name, long version) { - return SharedLibraryHelper.getSharedLibraryInfo( - name, version, mSharedLibraries, null); + return mSharedLibraries.getSharedLibraryInfo(name, version); } /** @@ -2058,7 +2053,7 @@ public class ComputerEngine implements Computer { // Is this a static library? WatchedLongSparseArray<SharedLibraryInfo> versionedLib = - mStaticLibsByDeclaringPackage.get(packageName); + mSharedLibraries.getStaticLibraryInfos(packageName); if (versionedLib == null || versionedLib.size() <= 0) { return packageName; } @@ -3056,54 +3051,8 @@ public class ComputerEngine implements Computer { } case DumpState.DUMP_LIBS: - { - boolean printedHeader = false; - final int numSharedLibraries = mSharedLibraries.size(); - for (int index = 0; index < numSharedLibraries; index++) { - final String libName = mSharedLibraries.keyAt(index); - final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = - mSharedLibraries.get(libName); - if (versionedLib == null) { - continue; - } - final int versionCount = versionedLib.size(); - for (int i = 0; i < versionCount; i++) { - SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); - if (!checkin) { - if (!printedHeader) { - if (dumpState.onTitlePrinted()) { - pw.println(); - } - pw.println("Libraries:"); - printedHeader = true; - } - pw.print(" "); - } else { - pw.print("lib,"); - } - pw.print(libraryInfo.getName()); - if (libraryInfo.isStatic()) { - pw.print(" version=" + libraryInfo.getLongVersion()); - } - if (!checkin) { - pw.print(" -> "); - } - if (libraryInfo.getPath() != null) { - if (libraryInfo.isNative()) { - pw.print(" (so) "); - } else { - pw.print(" (jar) "); - } - pw.print(libraryInfo.getPath()); - } else { - pw.print(" (apk) "); - pw.print(libraryInfo.getPackageName()); - } - pw.println(); - } - } + mSharedLibraries.dump(pw, dumpState); break; - } case DumpState.DUMP_PREFERRED: mSettings.dumpPreferred(pw, dumpState, packageName); @@ -3544,7 +3493,7 @@ public class ComputerEngine implements Computer { @NonNull @Override public WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getSharedLibraries() { - return mSharedLibraries; + return mSharedLibraries.getAll(); } @NonNull @@ -5487,12 +5436,11 @@ public class ComputerEngine implements Computer { } PackageDexUsage.PackageUseInfo packageUseInfo = mDexManager.getPackageUseInfoOrDefault(packageState.getPackageName()); - if (PackageManagerServiceUtils - .isUnusedSinceTimeInMillis(packageState.getFirstInstallTime(), - currentTimeInMillis, downgradeTimeThresholdMillis, packageUseInfo, - packageState.getTransientState().getLatestPackageUseTimeInMills(), - packageState.getTransientState() - .getLatestForegroundPackageUseTimeInMills())) { + if (PackageManagerServiceUtils.isUnusedSinceTimeInMillis( + PackageStateUtils.getEarliestFirstInstallTime(packageState.getUserStates()), + currentTimeInMillis, downgradeTimeThresholdMillis, packageUseInfo, + packageState.getTransientState().getLatestPackageUseTimeInMills(), + packageState.getTransientState().getLatestForegroundPackageUseTimeInMills())) { unusedPackages.add(packageState.getPackageName()); } } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index e84d990b5d21..9a80a4e558c4 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -547,7 +547,6 @@ final class DeletePackageHelper { true /*notLaunched*/, false /*hidden*/, 0 /*distractionFlags*/, - false /*suspended*/, null /*suspendParams*/, false /*instantApp*/, false /*virtualPreload*/, @@ -557,7 +556,8 @@ final class DeletePackageHelper { PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, - null /*splashScreenTheme*/); + null /*splashScreenTheme*/, + 0 /*firstInstallTime*/); } mPm.mSettings.writeKernelMappingLPr(ps); } diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java index 47e94f37ec8c..55d1293c616a 100644 --- a/services/core/java/com/android/server/pm/DumpHelper.java +++ b/services/core/java/com/android/server/pm/DumpHelper.java @@ -25,7 +25,6 @@ import android.content.ComponentName; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.SharedLibraryInfo; import android.os.Binder; import android.os.UserHandle; import android.os.incremental.PerUidReadTimeouts; @@ -37,7 +36,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; -import com.android.server.utils.WatchedLongSparseArray; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -710,7 +708,7 @@ final class DumpHelper { proto.end(verifierPackageToken); } - dumpSharedLibrariesProto(proto); + mPm.mInjector.getSharedLibrariesImpl().dumpProto(proto); dumpFeaturesProto(proto); mPm.mSettings.dumpPackagesProto(proto); mPm.mSettings.dumpSharedUsersProto(proto); @@ -728,33 +726,4 @@ final class DumpHelper { } } } - - private void dumpSharedLibrariesProto(ProtoOutputStream proto) { - final int count = mPm.mSharedLibraries.size(); - for (int i = 0; i < count; i++) { - final String libName = mPm.mSharedLibraries.keyAt(i); - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = - mPm.mSharedLibraries.get(libName); - if (versionedLib == null) { - continue; - } - final int versionCount = versionedLib.size(); - for (int j = 0; j < versionCount; j++) { - final SharedLibraryInfo libraryInfo = versionedLib.valueAt(j); - final long sharedLibraryToken = - proto.start(PackageServiceDumpProto.SHARED_LIBRARIES); - proto.write(PackageServiceDumpProto.SharedLibraryProto.NAME, libraryInfo.getName()); - final boolean isJar = (libraryInfo.getPath() != null); - proto.write(PackageServiceDumpProto.SharedLibraryProto.IS_JAR, isJar); - if (isJar) { - proto.write(PackageServiceDumpProto.SharedLibraryProto.PATH, - libraryInfo.getPath()); - } else { - proto.write(PackageServiceDumpProto.SharedLibraryProto.APK, - libraryInfo.getPackageName()); - } - proto.end(sharedLibraryToken); - } - } - } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 8573585c3c6b..27b6282055a2 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -207,6 +207,7 @@ final class InstallPackageHelper { private final PackageAbiHelper mPackageAbiHelper; private final ViewCompiler mViewCompiler; private final IBackupManager mIBackupManager; + private final SharedLibrariesImpl mSharedLibraries; // TODO(b/198166813): remove PMS dependency InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { @@ -226,6 +227,7 @@ final class InstallPackageHelper { mPackageAbiHelper = pm.mInjector.getAbiHelper(); mViewCompiler = pm.mInjector.getViewCompiler(); mIBackupManager = pm.mInjector.getIBackupManager(); + mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); } InstallPackageHelper(PackageManagerService pm) { @@ -325,7 +327,7 @@ final class InstallPackageHelper { } if (reconciledPkg.mCollectedSharedLibraryInfos != null) { - mPm.executeSharedLibrariesUpdateLPr(pkg, pkgSetting, null, null, + mSharedLibraries.executeSharedLibrariesUpdateLPw(pkg, pkgSetting, null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers); } @@ -388,13 +390,13 @@ final class InstallPackageHelper { synchronized (mPm.mLock) { if (!ArrayUtils.isEmpty(reconciledPkg.mAllowedSharedLibraryInfos)) { for (SharedLibraryInfo info : reconciledPkg.mAllowedSharedLibraryInfos) { - mPm.commitSharedLibraryInfoLocked(info); + mSharedLibraries.commitSharedLibraryInfoLPw(info); } final Map<String, AndroidPackage> combinedSigningDetails = reconciledPkg.getCombinedAvailablePackages(); try { // Shared libraries for the package need to be updated. - mPm.updateSharedLibrariesLocked(pkg, pkgSetting, null, null, + mSharedLibraries.updateSharedLibrariesLPw(pkg, pkgSetting, null, null, combinedSigningDetails); } catch (PackageManagerException e) { Slog.e(TAG, "updateSharedLibrariesLPr failed: ", e); @@ -402,7 +404,7 @@ final class InstallPackageHelper { // Update all applications that use this library. Skip when booting // since this will be done after all packages are scaned. if ((scanFlags & SCAN_BOOTING) == 0) { - clientLibPkgs = mPm.updateAllSharedLibrariesLocked(pkg, pkgSetting, + clientLibPkgs = mSharedLibraries.updateAllSharedLibrariesLPw(pkg, pkgSetting, combinedSigningDetails); } } @@ -559,6 +561,7 @@ final class InstallPackageHelper { pkgSetting.setHidden(false, userId); pkgSetting.setInstallReason(installReason, userId); pkgSetting.setUninstallReason(PackageManager.UNINSTALL_REASON_UNKNOWN, userId); + pkgSetting.setFirstInstallTime(System.currentTimeMillis(), userId); mPm.mSettings.writePackageRestrictionsLPr(userId); mPm.mSettings.writeKernelMappingLPr(pkgSetting); installed = true; @@ -855,7 +858,7 @@ final class InstallPackageHelper { mPm.notifyInstallObserver(request.mInstallResult, request.mArgs.mObserver); } - @GuardedBy("mInstallLock") + @GuardedBy("mPm.mInstallLock") private void installPackagesTracedLI(List<InstallRequest> requests) { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); @@ -884,7 +887,7 @@ final class InstallPackageHelper { * * Failure at any phase will result in a full failure to install all packages. */ - @GuardedBy("mInstallLock") + @GuardedBy("mPm.mInstallLock") private void installPackagesLI(List<InstallRequest> requests) { final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size()); final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size()); @@ -939,17 +942,26 @@ final class InstallPackageHelper { if (result.needsNewAppId()) { request.mInstallResult.mRemovedInfo.mAppIdChanging = true; } + if (!checkNoAppStorageIsConsistent( + result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) { + // TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible + // signatures. Is there a better error code? + request.mInstallResult.setError( + INSTALL_FAILED_UPDATE_INCOMPATIBLE, + "Update attempted to change value of " + + PackageManager.PROPERTY_NO_APP_DATA_STORAGE); + return; + } createdAppId.put(packageName, optimisticallyRegisterAppId(result)); versionInfos.put(result.mPkgSetting.getPkg().getPackageName(), mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg())); - if (result.mStaticSharedLibraryInfo != null - || result.mSdkSharedLibraryInfo != null) { - final PackageSetting sharedLibLatestVersionSetting = - mPm.getSharedLibLatestVersionSetting(result); - if (sharedLibLatestVersionSetting != null) { + if (result.mStaticSharedLibraryInfo != null) { + final PackageSetting staticSharedLibLatestVersionSetting = + mSharedLibraries.getStaticSharedLibLatestVersionSetting(result); + if (staticSharedLibLatestVersionSetting != null) { lastStaticSharedLibSettings.put( result.mPkgSetting.getPkg().getPackageName(), - sharedLibLatestVersionSetting); + staticSharedLibLatestVersionSetting); } } } catch (PackageManagerException e) { @@ -961,7 +973,7 @@ final class InstallPackageHelper { reconcileRequest = new ReconcileRequest(preparedScans, installArgs, installResults, prepareResults, - mPm.mSharedLibraries, + mSharedLibraries.getAll(), Collections.unmodifiableMap(mPm.mPackages), versionInfos, lastStaticSharedLibSettings); CommitRequest commitRequest = null; @@ -1036,7 +1048,23 @@ final class InstallPackageHelper { } } - @GuardedBy("mInstallLock") + @GuardedBy("mPm.mInstallLock") + private boolean checkNoAppStorageIsConsistent(AndroidPackage oldPkg, AndroidPackage newPkg) { + if (oldPkg == null) { + // New install, nothing to check against. + return true; + } + final PackageManager.Property curProp = + oldPkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); + final PackageManager.Property newProp = + newPkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); + if (curProp == null || !curProp.getBoolean()) { + return newProp == null || !newProp.getBoolean(); + } + return newProp != null && newProp.getBoolean(); + } + + @GuardedBy("mPm.mInstallLock") private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res) throws PrepareFailure { final int installFlags = args.mInstallFlags; @@ -1207,7 +1235,7 @@ final class InstallPackageHelper { // the package setting for the latest library version. PackageSetting signatureCheckPs = ps; if (parsedPackage.isStaticSharedLibrary()) { - SharedLibraryInfo libraryInfo = mPm.getLatestSharedLibraVersionLPr( + SharedLibraryInfo libraryInfo = mSharedLibraries.getLatestSharedLibraVersionLPr( parsedPackage); if (libraryInfo != null) { signatureCheckPs = mPm.mSettings.getPackageLPr( @@ -1880,7 +1908,7 @@ final class InstallPackageHelper { } } - @GuardedBy("mLock") + @GuardedBy("mPm.mLock") private void commitPackagesLocked(final CommitRequest request) { // TODO: remove any expected failures from this method; this should only be able to fail due // to unavoidable errors (I/O, etc.) @@ -1900,7 +1928,7 @@ final class InstallPackageHelper { PackageStateInternal deletedPkgSetting = mPm.getPackageStateInternal( oldPackage.getPackageName()); reconciledPkg.mPkgSetting - .setFirstInstallTime(deletedPkgSetting.getFirstInstallTime()) + .setFirstInstallTimeFromReplaced(deletedPkgSetting, request.mAllUsers) .setLastUpdateTime(System.currentTimeMillis()); res.mRemovedInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( @@ -1997,7 +2025,7 @@ final class InstallPackageHelper { ApplicationPackageManager.invalidateGetPackagesForUidCache(); } - @GuardedBy("mLock") + @GuardedBy("mPm.mLock") private boolean disableSystemPackageLPw(AndroidPackage oldPkg) { return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true); } @@ -2969,7 +2997,8 @@ final class InstallPackageHelper { synchronized (mPm.mLock) { mAppDataHelper.prepareAppDataAfterInstallLIF(pkg); try { - mPm.updateSharedLibrariesLocked(pkg, stubPkgSetting, null, null, + mSharedLibraries.updateSharedLibrariesLPw( + pkg, stubPkgSetting, null, null, Collections.unmodifiableMap(mPm.mPackages)); } catch (PackageManagerException e) { Slog.w(TAG, "updateAllSharedLibrariesLPw failed: ", e); @@ -3180,7 +3209,7 @@ final class InstallPackageHelper { try { // update shared libraries for the newly re-installed system package - mPm.updateSharedLibrariesLocked(pkg, pkgSetting, null, null, + mSharedLibraries.updateSharedLibrariesLPw(pkg, pkgSetting, null, null, Collections.unmodifiableMap(mPm.mPackages)); } catch (PackageManagerException e) { Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); @@ -3555,23 +3584,20 @@ final class InstallPackageHelper { boolean appIdCreated = false; try { final String pkgName = scanResult.mPkgSetting.getPackageName(); + final ReconcileRequest reconcileRequest = new ReconcileRequest( + Collections.singletonMap(pkgName, scanResult), + mSharedLibraries.getAll(), mPm.mPackages, + Collections.singletonMap(pkgName, + mPm.getSettingsVersionForPackage(parsedPackage)), + Collections.singletonMap(pkgName, + mSharedLibraries.getStaticSharedLibLatestVersionSetting( + scanResult))); final Map<String, ReconciledPackage> reconcileResult = - ReconcilePackageUtils.reconcilePackages( - new ReconcileRequest( - Collections.singletonMap(pkgName, scanResult), - mPm.mSharedLibraries, - mPm.mPackages, - Collections.singletonMap( - pkgName, - mPm.getSettingsVersionForPackage( - parsedPackage)), - Collections.singletonMap(pkgName, - mPm.getSharedLibLatestVersionSetting( - scanResult))), + ReconcilePackageUtils.reconcilePackages(reconcileRequest, mPm.mSettings.getKeySetManagerService(), mPm.mInjector); appIdCreated = optimisticallyRegisterAppId(scanResult); - commitReconciledScanResultLocked( - reconcileResult.get(pkgName), mPm.mUserManager.getUserIds()); + commitReconciledScanResultLocked(reconcileResult.get(pkgName), + mPm.mUserManager.getUserIds()); } catch (PackageManagerException e) { if (appIdCreated) { cleanUpAppIdCreation(scanResult); @@ -4192,8 +4218,8 @@ final class InstallPackageHelper { long minVersionCode = Long.MIN_VALUE; long maxVersionCode = Long.MAX_VALUE; - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get( - pkg.getStaticSharedLibName()); + WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + mSharedLibraries.getSharedLibraryInfos(pkg.getStaticSharedLibName()); if (versionedLib != null) { final int versionCount = versionedLib.size(); for (int i = 0; i < versionCount; i++) { diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index f7d4dbaa4ed7..1de239e0e8b6 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -59,6 +59,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; @@ -854,7 +855,10 @@ class InstantAppRegistry implements Watchable, Snappable { } else if (lhsPs.getTransientState().getLatestPackageUseTimeInMills() < rhsPs.getTransientState().getLatestPackageUseTimeInMills()) { return -1; - } else if (lhsPs.getFirstInstallTime() > rhsPs.getFirstInstallTime()) { + } else if ( + PackageStateUtils.getEarliestFirstInstallTime(lhsPs.getUserStates()) + > PackageStateUtils.getEarliestFirstInstallTime( + rhsPs.getUserStates())) { return 1; } else { return -1; diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 0777cdece8e7..ca876853e3a0 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -88,6 +88,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; +import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -103,6 +104,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.concurrent.ExecutionException; /** * Service that manages requests and callbacks for launchers that support @@ -728,9 +730,16 @@ public class LauncherAppsService extends SystemService { return null; } - final Intent[] intents = mShortcutServiceInternal.createShortcutIntents( - getCallingUserId(), callingPackage, packageName, shortcutId, - user.getIdentifier(), injectBinderCallingPid(), injectBinderCallingUid()); + final AndroidFuture<Intent[]> ret = new AndroidFuture<>(); + Intent[] intents; + mShortcutServiceInternal.createShortcutIntentsAsync(getCallingUserId(), + callingPackage, packageName, shortcutId, user.getIdentifier(), + injectBinderCallingPid(), injectBinderCallingUid(), ret); + try { + intents = ret.get(); + } catch (InterruptedException | ExecutionException e) { + return null; + } if (intents == null || intents.length == 0) { return null; } @@ -901,6 +910,40 @@ public class LauncherAppsService extends SystemService { } @Override + public void getShortcutsAsync(@NonNull final String callingPackage, + @NonNull final ShortcutQueryWrapper query, @NonNull final UserHandle targetUser, + @NonNull final AndroidFuture<List<ShortcutInfo>> cb) { + ensureShortcutPermission(callingPackage); + if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) { + cb.complete(Collections.EMPTY_LIST); + return; + } + + final long changedSince = query.getChangedSince(); + final String packageName = query.getPackage(); + final List<String> shortcutIds = query.getShortcutIds(); + final List<LocusId> locusIds = query.getLocusIds(); + final ComponentName componentName = query.getActivity(); + final int flags = query.getQueryFlags(); + if (shortcutIds != null && packageName == null) { + throw new IllegalArgumentException( + "To query by shortcut ID, package name must also be set"); + } + if (locusIds != null && packageName == null) { + throw new IllegalArgumentException( + "To query by locus ID, package name must also be set"); + } + if ((query.getQueryFlags() & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { + ensureStrictAccessShortcutsPermission(callingPackage); + } + + mShortcutServiceInternal.getShortcutsAsync(getCallingUserId(), + callingPackage, changedSince, packageName, shortcutIds, locusIds, + componentName, flags, targetUser.getIdentifier(), + injectBinderCallingPid(), injectBinderCallingUid(), cb); + } + + @Override public void registerShortcutChangeCallback(@NonNull final String callingPackage, @NonNull final ShortcutQueryWrapper query, @NonNull final IShortcutChangeCallback callback) { @@ -991,8 +1034,14 @@ public class LauncherAppsService extends SystemService { return null; } - return mShortcutServiceInternal.getShortcutIconFd(getCallingUserId(), - callingPackage, packageName, id, targetUserId); + final AndroidFuture<ParcelFileDescriptor> ret = new AndroidFuture<>(); + mShortcutServiceInternal.getShortcutIconFdAsync(getCallingUserId(), + callingPackage, packageName, id, targetUserId, ret); + try { + return ret.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } @Override @@ -1003,8 +1052,14 @@ public class LauncherAppsService extends SystemService { return null; } - return mShortcutServiceInternal.getShortcutIconUri(getCallingUserId(), callingPackage, - packageName, shortcutId, userId); + final AndroidFuture<String> ret = new AndroidFuture<>(); + mShortcutServiceInternal.getShortcutIconUriAsync(getCallingUserId(), callingPackage, + packageName, shortcutId, userId, ret); + try { + return ret.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } @Override @@ -1037,9 +1092,16 @@ public class LauncherAppsService extends SystemService { ensureShortcutPermission(callerUid, callerPid, callingPackage); } - final Intent[] intents = mShortcutServiceInternal.createShortcutIntents( - callingUserId, callingPackage, packageName, shortcutId, targetUserId, - callerPid, callerUid); + final AndroidFuture<Intent[]> ret = new AndroidFuture<>(); + Intent[] intents; + mShortcutServiceInternal.createShortcutIntentsAsync(getCallingUserId(), callingPackage, + packageName, shortcutId, targetUserId, + injectBinderCallingPid(), injectBinderCallingUid(), ret); + try { + intents = ret.get(); + } catch (InterruptedException | ExecutionException e) { + return false; + } if (intents == null || intents.length == 0) { return false; } diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java index 217bc2397f29..d1ea41ae5613 100644 --- a/services/core/java/com/android/server/pm/PackageHandler.java +++ b/services/core/java/com/android/server/pm/PackageHandler.java @@ -388,7 +388,8 @@ final class PackageHandler extends Handler { } case PRUNE_UNUSED_STATIC_SHARED_LIBRARIES: { try { - mPm.pruneUnusedStaticSharedLibraries(Long.MAX_VALUE, + mPm.mInjector.getSharedLibrariesImpl().pruneUnusedStaticSharedLibraries( + Long.MAX_VALUE, Settings.Global.getLong(mPm.mContext.getContentResolver(), Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD)); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index f4740448b8a9..cfcf199d01a4 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -404,7 +404,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) { // Aggressively close old sessions because we are running low on storage // Their staging dirs will be removed too - session.abandon(); + PackageInstallerSession root = !session.hasParentSessionId() + ? session : mSessions.get(session.getParentSessionId()); + if (!root.isDestroyed()) { + root.abandon(); + } } else { // Session is new enough, so it deserves to be kept even on low storage unclaimedStagingDirsOnVolume.remove(session.stageDir); @@ -1623,7 +1627,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements progress); } - public void onStagedSessionChanged(PackageInstallerSession session) { + public void onSessionChanged(PackageInstallerSession session) { session.markUpdated(); mSettingsWriteRequest.schedule(); if (mOkToSendBroadcasts && !session.isDestroyed()) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index a94985c226ea..f45e54b04e54 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -81,7 +81,7 @@ import android.content.pm.InstallationFileParcel; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; +import android.content.pm.PackageInstaller.SessionInfo.SessionErrorCode; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -151,7 +151,6 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; -import com.android.server.SystemConfig; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -229,8 +228,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_IS_READY = "isReady"; private static final String ATTR_IS_FAILED = "isFailed"; private static final String ATTR_IS_APPLIED = "isApplied"; - private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode"; - private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage"; + private static final String ATTR_SESSION_ERROR_CODE = "errorCode"; + private static final String ATTR_SESSION_ERROR_MESSAGE = "errorMessage"; private static final String ATTR_MODE = "mode"; private static final String ATTR_INSTALL_FLAGS = "installFlags"; private static final String ATTR_INSTALL_LOCATION = "installLocation"; @@ -454,22 +453,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private ArrayMap<String, PerFileChecksum> mChecksums = new ArrayMap<>(); + @GuardedBy("mLock") + private boolean mSessionApplied; + @GuardedBy("mLock") + private boolean mSessionReady; + @GuardedBy("mLock") + private boolean mSessionFailed; + @GuardedBy("mLock") + private int mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; + @GuardedBy("mLock") + private String mSessionErrorMessage; + @Nullable final StagedSession mStagedSession; @VisibleForTesting public class StagedSession implements StagingManager.StagedSession { - @GuardedBy("mLock") - private boolean mSessionApplied; - @GuardedBy("mLock") - private boolean mSessionReady; - @GuardedBy("mLock") - private boolean mSessionFailed; - @GuardedBy("mLock") - private int mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; - @GuardedBy("mLock") - private String mSessionErrorMessage; - /** * The callback to run when pre-reboot verification has ended. Used by {@link #abandon()} * to delay session clean-up until it is safe to do so. @@ -478,15 +477,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Nullable private Runnable mPendingAbandonCallback; - StagedSession(boolean isReady, boolean isApplied, boolean isFailed, int errorCode, - String errorMessage) { - mSessionReady = isReady; - mSessionApplied = isApplied; - mSessionFailed = isFailed; - mSessionErrorCode = errorCode; - mSessionErrorMessage = errorMessage != null ? errorMessage : ""; - } - @Override public List<StagingManager.StagedSession> getChildSessions() { if (!params.isMultiPackage) { @@ -534,52 +524,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void setSessionReady() { - synchronized (mLock) { - // Do not allow destroyed/failed staged session to change state - if (mDestroyed || mSessionFailed) return; - mSessionReady = true; - mSessionApplied = false; - mSessionFailed = false; - mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; - mSessionErrorMessage = ""; - } - mCallback.onStagedSessionChanged(PackageInstallerSession.this); + PackageInstallerSession.this.setSessionReady(); } @Override public void setSessionFailed(int errorCode, String errorMessage) { - List<PackageInstallerSession> childSessions; - synchronized (mLock) { - // Do not allow destroyed/failed staged session to change state - if (mDestroyed || mSessionFailed) return; - mSessionReady = false; - mSessionApplied = false; - mSessionFailed = true; - mSessionErrorCode = errorCode; - mSessionErrorMessage = errorMessage; - Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage); - childSessions = getChildSessionsLocked(); - } - destroy(); - mCallback.onStagedSessionChanged(PackageInstallerSession.this); + PackageInstallerSession.this.setSessionFailed(errorCode, errorMessage); } @Override public void setSessionApplied() { - List<PackageInstallerSession> childSessions; - synchronized (mLock) { - // Do not allow destroyed/failed staged session to change state - if (mDestroyed || mSessionFailed) return; - mSessionReady = false; - mSessionApplied = true; - mSessionFailed = false; - mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; - mSessionErrorMessage = ""; - Slog.d(TAG, "Marking session " + sessionId + " as applied"); - childSessions = getChildSessionsLocked(); - } - destroy(); - mCallback.onStagedSessionChanged(PackageInstallerSession.this); + PackageInstallerSession.this.setSessionApplied(); } @Override @@ -656,35 +611,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public boolean isSessionReady() { - synchronized (mLock) { - return mSessionReady; - } + return PackageInstallerSession.this.isSessionReady(); } @Override public boolean isSessionApplied() { - synchronized (mLock) { - return mSessionApplied; - } + return PackageInstallerSession.this.isSessionApplied(); } @Override public boolean isSessionFailed() { - synchronized (mLock) { - return mSessionFailed; - } - } - - @StagedSessionErrorCode int getSessionErrorCode() { - synchronized (mLock) { - return mSessionErrorCode; - } - } - - String getSessionErrorMessage() { - synchronized (mLock) { - return mSessionErrorMessage; - } + return PackageInstallerSession.this.isSessionFailed(); } @Override @@ -714,7 +651,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (mStageDirInUse) { // Pre-reboot verification is ongoing, not safe to clean up the session yet. mPendingAbandonCallback = r; - mCallback.onStagedSessionChanged(PackageInstallerSession.this); + mCallback.onSessionChanged(PackageInstallerSession.this); return; } } @@ -1015,8 +952,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { ArrayMap<String, PerFileChecksum> checksums, boolean prepared, boolean committed, boolean destroyed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, - boolean isFailed, boolean isApplied, int stagedSessionErrorCode, - String stagedSessionErrorMessage) { + boolean isFailed, boolean isApplied, int sessionErrorCode, + String sessionErrorMessage) { mCallback = callback; mContext = context; mPm = pm; @@ -1071,8 +1008,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPrepared = prepared; mCommitted.set(committed); mDestroyed = destroyed; - mStagedSession = params.isStaged ? new StagedSession(isReady, isApplied, isFailed, - stagedSessionErrorCode, stagedSessionErrorMessage) : null; + mSessionReady = isReady; + mSessionApplied = isApplied; + mSessionFailed = isFailed; + mSessionErrorCode = sessionErrorCode; + mSessionErrorMessage = + sessionErrorMessage != null ? sessionErrorMessage : ""; + mStagedSession = params.isStaged ? new StagedSession() : null; if (isDataLoaderInstallation()) { if (isApexSession()) { @@ -1173,11 +1115,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.rollbackDataPolicy = params.rollbackDataPolicy; info.parentSessionId = mParentSessionId; info.childSessionIds = getChildSessionIdsLocked(); - info.isStagedSessionApplied = isStagedSessionApplied(); - info.isStagedSessionReady = isStagedSessionReady(); - info.isStagedSessionFailed = isStagedSessionFailed(); - info.setStagedSessionErrorCode(getStagedSessionErrorCode(), - getStagedSessionErrorMessage()); + info.isSessionApplied = mSessionApplied; + info.isSessionReady = mSessionReady; + info.isSessionFailed = mSessionFailed; + info.setSessionErrorCode(mSessionErrorCode, mSessionErrorMessage); info.createdMillis = createdMillis; info.updatedMillis = updatedMillis; info.requireUserAction = params.requireUserAction; @@ -2229,7 +2170,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final PackageInstallerSession root = hasParentSessionId() ? allSessions.get(getParentSessionId()) : this; - if (root != null) { + if (root != null && !root.isStagedAndInTerminalState()) { if (isApexSession()) { validateApexInstallLocked(); } else { @@ -2357,28 +2298,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return; } - - // Check if APEX update is allowed. We do this check in handleInstall, since this is one of - // the places that: - // * Shared between staged and non-staged APEX update flows. - // * Only is called after boot completes. - // The later is important, since isApexUpdateAllowed check depends on the - // ModuleInfoProvider, which is only populated after device has booted. - if (isApexSession()) { - boolean checkApexUpdateAllowed = - (params.installFlags & PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK) - == 0; - synchronized (mLock) { - if (checkApexUpdateAllowed && !isApexUpdateAllowed(mPackageName, - mInstallSource.installerPackageName)) { - onSessionValidationFailure(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, - "Update of APEX package " + mPackageName + " is not allowed for " - + mInstallSource.installerPackageName); - return; - } - } - } - if (params.isStaged) { // TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even // though ideally, we just need to send session committed broadcast. @@ -2825,25 +2744,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return sessionContains((s) -> !s.isApexSession()); } - private boolean isApexUpdateAllowed(String apexPackageName, String installerPackageName) { - if (mPm.getModuleInfo(apexPackageName, 0) != null) { - final String modulesInstaller = - SystemConfig.getInstance().getModulesInstallerPackageName(); - if (modulesInstaller == null) { - Slog.w(TAG, "No modules installer defined"); - return false; - } - return modulesInstaller.equals(installerPackageName); - } - final String vendorApexInstaller = - SystemConfig.getInstance().getAllowedVendorApexes().get(apexPackageName); - if (vendorApexInstaller == null) { - Slog.w(TAG, apexPackageName + " is not allowed to be updated"); - return false; - } - return vendorApexInstaller.equals(installerPackageName); - } - /** * Validate apex install. * <p> @@ -4261,30 +4161,83 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private void setSessionReady() { + synchronized (mLock) { + // Do not allow destroyed/failed session to change state + if (mDestroyed || mSessionFailed) return; + mSessionReady = true; + mSessionApplied = false; + mSessionFailed = false; + mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; + mSessionErrorMessage = ""; + } + mCallback.onSessionChanged(this); + } + + private void setSessionFailed(int errorCode, String errorMessage) { + synchronized (mLock) { + // Do not allow destroyed/failed session to change state + if (mDestroyed || mSessionFailed) return; + mSessionReady = false; + mSessionApplied = false; + mSessionFailed = true; + mSessionErrorCode = errorCode; + mSessionErrorMessage = errorMessage; + Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage); + } + destroy(); + mCallback.onSessionChanged(this); + } + + private void setSessionApplied() { + synchronized (mLock) { + // Do not allow destroyed/failed session to change state + if (mDestroyed || mSessionFailed) return; + mSessionReady = false; + mSessionApplied = true; + mSessionFailed = false; + mSessionErrorCode = SessionInfo.STAGED_SESSION_NO_ERROR; + mSessionErrorMessage = ""; + Slog.d(TAG, "Marking session " + sessionId + " as applied"); + } + destroy(); + mCallback.onSessionChanged(this); + } + /** {@hide} */ - boolean isStagedSessionReady() { - return params.isStaged && mStagedSession.isSessionReady(); + boolean isSessionReady() { + synchronized (mLock) { + return mSessionReady; + } } /** {@hide} */ - boolean isStagedSessionApplied() { - return params.isStaged && mStagedSession.isSessionApplied(); + boolean isSessionApplied() { + synchronized (mLock) { + return mSessionApplied; + } } /** {@hide} */ - boolean isStagedSessionFailed() { - return params.isStaged && mStagedSession.isSessionFailed(); + boolean isSessionFailed() { + synchronized (mLock) { + return mSessionFailed; + } } /** {@hide} */ - @StagedSessionErrorCode int getStagedSessionErrorCode() { - return params.isStaged ? mStagedSession.getSessionErrorCode() - : SessionInfo.STAGED_SESSION_NO_ERROR; + @SessionErrorCode + int getSessionErrorCode() { + synchronized (mLock) { + return mSessionErrorCode; + } } /** {@hide} */ - String getStagedSessionErrorMessage() { - return params.isStaged ? mStagedSession.getSessionErrorMessage() : ""; + String getSessionErrorMessage() { + synchronized (mLock) { + return mSessionErrorMessage; + } } /** @@ -4386,11 +4339,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("params.isStaged", params.isStaged); pw.printPair("mParentSessionId", mParentSessionId); pw.printPair("mChildSessionIds", getChildSessionIdsLocked()); - pw.printPair("mStagedSessionApplied", isStagedSessionApplied()); - pw.printPair("mStagedSessionFailed", isStagedSessionFailed()); - pw.printPair("mStagedSessionReady", isStagedSessionReady()); - pw.printPair("mStagedSessionErrorCode", getStagedSessionErrorCode()); - pw.printPair("mStagedSessionErrorMessage", getStagedSessionErrorMessage()); + pw.printPair("mSessionApplied", mSessionApplied); + pw.printPair("mSessionFailed", mSessionFailed); + pw.printPair("mSessionReady", mSessionReady); + pw.printPair("mSessionErrorCode", mSessionErrorCode); + pw.printPair("mSessionErrorMessage", mSessionErrorMessage); pw.println(); pw.decreaseIndent(); @@ -4556,12 +4509,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged); - writeBooleanAttribute(out, ATTR_IS_READY, isStagedSessionReady()); - writeBooleanAttribute(out, ATTR_IS_FAILED, isStagedSessionFailed()); - writeBooleanAttribute(out, ATTR_IS_APPLIED, isStagedSessionApplied()); - out.attributeInt(null, ATTR_STAGED_SESSION_ERROR_CODE, getStagedSessionErrorCode()); - writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE, - getStagedSessionErrorMessage()); + writeBooleanAttribute(out, ATTR_IS_READY, mSessionReady); + writeBooleanAttribute(out, ATTR_IS_FAILED, mSessionFailed); + writeBooleanAttribute(out, ATTR_IS_APPLIED, mSessionApplied); + out.attributeInt(null, ATTR_SESSION_ERROR_CODE, mSessionErrorCode); + writeStringAttribute(out, ATTR_SESSION_ERROR_MESSAGE, mSessionErrorMessage); // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after // we've read all sessions. out.attributeInt(null, ATTR_PARENT_SESSION_ID, mParentSessionId); @@ -4752,10 +4704,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isReady = in.getAttributeBoolean(null, ATTR_IS_READY, false); final boolean isFailed = in.getAttributeBoolean(null, ATTR_IS_FAILED, false); final boolean isApplied = in.getAttributeBoolean(null, ATTR_IS_APPLIED, false); - final int stagedSessionErrorCode = in.getAttributeInt(null, ATTR_STAGED_SESSION_ERROR_CODE, + final int sessionErrorCode = in.getAttributeInt(null, ATTR_SESSION_ERROR_CODE, SessionInfo.STAGED_SESSION_NO_ERROR); - final String stagedSessionErrorMessage = readStringAttribute(in, - ATTR_STAGED_SESSION_ERROR_MESSAGE); + final String sessionErrorMessage = readStringAttribute(in, ATTR_SESSION_ERROR_MESSAGE); if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) { throw new IllegalArgumentException("Can't restore staged session with invalid state."); @@ -4869,6 +4820,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { installerUid, installSource, params, createdMillis, committedMillis, stageDir, stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, - stagedSessionErrorCode, stagedSessionErrorMessage); + sessionErrorCode, sessionErrorMessage); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f3fe2c07d3b4..248944e476ad 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -239,6 +239,9 @@ import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SuspendParams; +import com.android.server.pm.pkg.mutate.PackageStateMutator; +import com.android.server.pm.pkg.mutate.PackageStateWrite; +import com.android.server.pm.pkg.mutate.PackageUserStateWrite; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationService; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; @@ -249,7 +252,6 @@ import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.utils.Watchable; import com.android.server.utils.Watched; import com.android.server.utils.WatchedArrayMap; -import com.android.server.utils.WatchedLongSparseArray; import com.android.server.utils.WatchedSparseBooleanArray; import com.android.server.utils.WatchedSparseIntArray; import com.android.server.utils.Watcher; @@ -279,7 +281,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -289,7 +290,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -597,6 +597,16 @@ public class PackageManagerService extends IPackageManager.Stub // the suffix "Locked". Some methods may use the legacy suffix "LP" final PackageManagerTracedLock mLock; + // Lock alias for doing package state mutation + private final PackageManagerTracedLock mPackageStateWriteLock; + + // Lock alias to track syncing a consistent Computer + private final PackageManagerTracedLock mLiveComputerSyncLock; + + private final PackageStateMutator mPackageStateMutator = new PackageStateMutator( + this::getPackageSettingForMutation, + this::getDisabledPackageSettingForMutation); + // Keys are String (package name), values are Package. @Watched @GuardedBy("mLock") @@ -757,19 +767,7 @@ public class PackageManagerService extends IPackageManager.Stub // Currently known shared libraries. @Watched - final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> - mSharedLibraries = new WatchedArrayMap<>(); - private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>> - mSharedLibrariesSnapshot = - new SnapshotCache.Auto<>(mSharedLibraries, mSharedLibraries, - "PackageManagerService.mSharedLibraries"); - @Watched - final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> - mStaticLibsByDeclaringPackage = new WatchedArrayMap<>(); - private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>> - mStaticLibsByDeclaringPackageSnapshot = - new SnapshotCache.Auto<>(mStaticLibsByDeclaringPackage, mStaticLibsByDeclaringPackage, - "PackageManagerService.mStaticLibsByDeclaringPackage"); + private final SharedLibrariesImpl mSharedLibraries; // Mapping from instrumentation class names to info about them. @Watched @@ -1002,8 +1000,6 @@ public class PackageManagerService extends IPackageManager.Stub public final Settings settings; public final WatchedSparseIntArray isolatedOwners; public final WatchedArrayMap<String, AndroidPackage> packages; - public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibs; - public final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> staticLibs; public final WatchedArrayMap<ComponentName, ParsedInstrumentation> instrumentation; public final WatchedSparseBooleanArray webInstantAppsDisabled; public final ComponentName resolveComponentName; @@ -1017,14 +1013,13 @@ public class PackageManagerService extends IPackageManager.Stub public final ComponentResolver componentResolver; public final PackageManagerService service; public final WatchedArrayMap<String, Integer> frozenPackages; + public final SharedLibrariesRead sharedLibraries; Snapshot(int type) { if (type == Snapshot.SNAPPED) { settings = mSettings.snapshot(); isolatedOwners = mIsolatedOwnersSnapshot.snapshot(); packages = mPackagesSnapshot.snapshot(); - sharedLibs = mSharedLibrariesSnapshot.snapshot(); - staticLibs = mStaticLibsByDeclaringPackageSnapshot.snapshot(); instrumentation = mInstrumentationSnapshot.snapshot(); resolveComponentName = mResolveComponentName.clone(); resolveActivity = new ActivityInfo(mResolveActivity); @@ -1043,12 +1038,11 @@ public class PackageManagerService extends IPackageManager.Stub appsFilter = mAppsFilter.snapshot(); componentResolver = mComponentResolver.snapshot(); frozenPackages = mFrozenPackagesSnapshot.snapshot(); + sharedLibraries = mSharedLibraries.snapshot(); } else if (type == Snapshot.LIVE) { settings = mSettings; isolatedOwners = mIsolatedOwners; packages = mPackages; - sharedLibs = mSharedLibraries; - staticLibs = mStaticLibsByDeclaringPackage; instrumentation = mInstrumentation; resolveComponentName = mResolveComponentName; resolveActivity = mResolveActivity; @@ -1061,6 +1055,7 @@ public class PackageManagerService extends IPackageManager.Stub appsFilter = mAppsFilter; componentResolver = mComponentResolver; frozenPackages = mFrozenPackages; + sharedLibraries = mSharedLibraries; } else { throw new IllegalArgumentException(); } @@ -1522,7 +1517,8 @@ public class PackageManagerService extends IPackageManager.Stub context::getSystemService, (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm), (i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService( - Context.BACKUP_SERVICE))); + Context.BACKUP_SERVICE)), + (i, pm) -> new SharedLibrariesImpl(pm, i)); if (Build.VERSION.SDK_INT <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); @@ -1594,7 +1590,6 @@ public class PackageManagerService extends IPackageManager.Stub private void registerObserver() { mPackages.registerObserver(mWatcher); mSharedLibraries.registerObserver(mWatcher); - mStaticLibsByDeclaringPackage.registerObserver(mWatcher); mInstrumentation.registerObserver(mWatcher); mWebInstantAppsDisabled.registerObserver(mWatcher); mAppsFilter.registerObserver(mWatcher); @@ -1626,11 +1621,14 @@ public class PackageManagerService extends IPackageManager.Stub mInstaller = injector.getInstaller(); mInstallLock = injector.getInstallLock(); mLock = injector.getLock(); + mPackageStateWriteLock = mLock; + mLiveComputerSyncLock = mLock; mPermissionManager = injector.getPermissionManagerServiceInternal(); mSettings = injector.getSettings(); mUserManager = injector.getUserManagerService(); mDomainVerificationManager = injector.getDomainVerificationManagerInternal(); mHandler = injector.getHandler(); + mSharedLibraries = injector.getSharedLibrariesImpl(); mApexManager = testParams.apexManager; mArtManagerService = testParams.artManagerService; @@ -1708,6 +1706,7 @@ public class PackageManagerService extends IPackageManager.Stub mPreferredActivityHelper = testParams.preferredActivityHelper; mResolveIntentHelper = testParams.resolveIntentHelper; mDexOptHelper = testParams.dexOptHelper; + mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); invalidatePackageInfoCache(); } @@ -1729,6 +1728,8 @@ public class PackageManagerService extends IPackageManager.Stub mInjector.bootstrap(this); mLock = injector.getLock(); + mPackageStateWriteLock = mLock; + mLiveComputerSyncLock = mLock; mInstallLock = injector.getInstallLock(); LockGuard.installLock(mLock, LockGuard.INDEX_PACKAGES); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START, @@ -1817,6 +1818,7 @@ public class PackageManagerService extends IPackageManager.Stub mArtManagerService = injector.getArtManagerService(); mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper()); mViewCompiler = injector.getViewCompiler(); + mSharedLibraries = mInjector.getSharedLibrariesImpl(); mContext.getSystemService(DisplayManager.class) .getDisplay(Display.DEFAULT_DISPLAY).getMetrics(mMetrics); @@ -1846,6 +1848,7 @@ public class PackageManagerService extends IPackageManager.Stub mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this); mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper, mAppDataHelper); + mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); mPreferredActivityHelper = new PreferredActivityHelper(this); mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper); mDexOptHelper = new DexOptHelper(this); @@ -1877,7 +1880,7 @@ public class PackageManagerService extends IPackageManager.Stub = systemConfig.getSharedLibraries(); final int builtInLibCount = libConfig.size(); for (int i = 0; i < builtInLibCount; i++) { - addBuiltInSharedLibraryLocked(libConfig.valueAt(i)); + mSharedLibraries.addBuiltInSharedLibraryLPw(libConfig.valueAt(i)); } // Now that we have added all the libraries, iterate again to add dependency @@ -1996,7 +1999,8 @@ public class PackageManagerService extends IPackageManager.Stub // Now that we know all of the shared libraries, update all clients to have // the correct library paths. - updateAllSharedLibrariesLocked(null, null, Collections.unmodifiableMap(mPackages)); + mSharedLibraries.updateAllSharedLibrariesLPw( + null, null, Collections.unmodifiableMap(mPackages)); for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) { // NOTE: We ignore potential failures here during a system scan (like @@ -2835,7 +2839,7 @@ public class PackageManagerService extends IPackageManager.Stub if (file.getUsableSpace() >= bytes) return; // 5. Consider shared libraries with refcount=0 and age>min cache period - if (internalVolume && pruneUnusedStaticSharedLibraries(bytes, + if (internalVolume && mSharedLibraries.pruneUnusedStaticSharedLibraries(bytes, android.provider.Settings.Global.getLong(mContext.getContentResolver(), Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) { @@ -2897,85 +2901,6 @@ public class PackageManagerService extends IPackageManager.Stub throw new IOException("Failed to free " + bytes + " on storage device at " + file); } - private PackageSetting getLibraryPackage(SharedLibraryInfo libInfo) { - final VersionedPackage declaringPackage = libInfo.getDeclaringPackage(); - if (libInfo.isStatic()) { - // Resolve the package name - we use synthetic package names internally - final String internalPackageName = resolveInternalPackageNameLPr( - declaringPackage.getPackageName(), - declaringPackage.getLongVersionCode()); - return mSettings.getPackageLPr(internalPackageName); - } - if (libInfo.isSdk()) { - return mSettings.getPackageLPr(declaringPackage.getPackageName()); - } - return null; - } - - boolean pruneUnusedStaticSharedLibraries(long neededSpace, long maxCachePeriod) - throws IOException { - final StorageManager storage = mInjector.getSystemService(StorageManager.class); - final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); - - List<VersionedPackage> packagesToDelete = null; - final long now = System.currentTimeMillis(); - - // Important: We skip shared libs used for some user since - // in such a case we need to keep the APK on the device. The check for - // a lib being used for any user is performed by the uninstall call. - synchronized (mLock) { - final int libCount = mSharedLibraries.size(); - for (int i = 0; i < libCount; i++) { - final WatchedLongSparseArray<SharedLibraryInfo> versionedLib - = mSharedLibraries.valueAt(i); - if (versionedLib == null) { - continue; - } - final int versionCount = versionedLib.size(); - for (int j = 0; j < versionCount; j++) { - SharedLibraryInfo libInfo = versionedLib.valueAt(j); - final PackageSetting ps = getLibraryPackage(libInfo); - if (ps == null) { - continue; - } - // Skip unused libs cached less than the min period to prevent pruning a lib - // needed by a subsequently installed package. - if (now - ps.getLastUpdateTime() < maxCachePeriod) { - continue; - } - - if (ps.getPkg().isSystem()) { - continue; - } - - if (packagesToDelete == null) { - packagesToDelete = new ArrayList<>(); - } - packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(), - libInfo.getDeclaringPackage().getLongVersionCode())); - } - } - } - - if (packagesToDelete != null) { - final int packageCount = packagesToDelete.size(); - for (int i = 0; i < packageCount; i++) { - final VersionedPackage pkgToDelete = packagesToDelete.get(i); - // Delete the package synchronously (will fail of the lib used for any user). - if (mDeletePackageHelper.deletePackageX(pkgToDelete.getPackageName(), - pkgToDelete.getLongVersionCode(), UserHandle.USER_SYSTEM, - PackageManager.DELETE_ALL_USERS, - true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) { - if (volume.getUsableSpace() >= neededSpace) { - return true; - } - } - } - } - - return false; - } - /** * Update given flags when being used to request {@link PackageInfo}. */ @@ -3961,40 +3886,6 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.getSharedLibraryInfo(name, version); } - SharedLibraryInfo getLatestSharedLibraVersionLPr(AndroidPackage pkg) { - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get( - pkg.getStaticSharedLibName()); - if (versionedLib == null) { - return null; - } - long previousLibVersion = -1; - final int versionCount = versionedLib.size(); - for (int i = 0; i < versionCount; i++) { - final long libVersion = versionedLib.keyAt(i); - if (libVersion < pkg.getStaticSharedLibVersion()) { - previousLibVersion = Math.max(previousLibVersion, libVersion); - } - } - if (previousLibVersion >= 0) { - return versionedLib.get(previousLibVersion); - } - return null; - } - - @Nullable - PackageSetting getSharedLibLatestVersionSetting(@NonNull ScanResult scanResult) { - PackageSetting sharedLibPackage = null; - synchronized (mLock) { - final SharedLibraryInfo latestSharedLibraVersionLPr = - getLatestSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage); - if (latestSharedLibraVersionLPr != null) { - sharedLibPackage = mSettings.getPackageLPr( - latestSharedLibraVersionLPr.getPackageName()); - } - } - return sharedLibPackage; - } - public void shutdown() { mCompilerStats.writeNow(); mDexManager.writePackageDexUsageNow(); @@ -4049,253 +3940,6 @@ public class PackageManagerService extends IPackageManager.Stub return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId }; } - @GuardedBy("mLock") - private void applyDefiningSharedLibraryUpdateLocked( - AndroidPackage pkg, SharedLibraryInfo libInfo, - BiConsumer<SharedLibraryInfo, SharedLibraryInfo> action) { - // Note that libraries defined by this package may be null if: - // - Package manager was unable to create the shared library. The package still - // gets installed, but the shared library does not get created. - // Or: - // - Package manager is in a state where package isn't scanned yet. This will - // get called again after scanning to fix the dependencies. - if (AndroidPackageUtils.isLibrary(pkg)) { - if (pkg.getSdkLibName() != null) { - SharedLibraryInfo definedLibrary = getSharedLibraryInfo( - pkg.getSdkLibName(), pkg.getSdkLibVersionMajor()); - if (definedLibrary != null) { - action.accept(definedLibrary, libInfo); - } - } else if (pkg.getStaticSharedLibName() != null) { - SharedLibraryInfo definedLibrary = getSharedLibraryInfo( - pkg.getStaticSharedLibName(), pkg.getStaticSharedLibVersion()); - if (definedLibrary != null) { - action.accept(definedLibrary, libInfo); - } - } else { - for (String libraryName : pkg.getLibraryNames()) { - SharedLibraryInfo definedLibrary = getSharedLibraryInfo( - libraryName, SharedLibraryInfo.VERSION_UNDEFINED); - if (definedLibrary != null) { - action.accept(definedLibrary, libInfo); - } - } - } - } - } - - @GuardedBy("mLock") - private void addSharedLibraryLPr(AndroidPackage pkg, Set<String> usesLibraryFiles, - SharedLibraryInfo libInfo, @Nullable AndroidPackage changingLib, - @Nullable PackageSetting changingLibSetting) { - if (libInfo.getPath() != null) { - usesLibraryFiles.add(libInfo.getPath()); - return; - } - AndroidPackage pkgForCodePaths = mPackages.get(libInfo.getPackageName()); - PackageSetting pkgSetting = mSettings.getPackageLPr(libInfo.getPackageName()); - if (changingLib != null && changingLib.getPackageName().equals(libInfo.getPackageName())) { - // If we are doing this while in the middle of updating a library apk, - // then we need to make sure to use that new apk for determining the - // dependencies here. (We haven't yet finished committing the new apk - // to the package manager state.) - if (pkgForCodePaths == null - || pkgForCodePaths.getPackageName().equals(changingLib.getPackageName())) { - pkgForCodePaths = changingLib; - pkgSetting = changingLibSetting; - } - } - if (pkgForCodePaths != null) { - usesLibraryFiles.addAll(AndroidPackageUtils.getAllCodePaths(pkgForCodePaths)); - // If the package provides libraries, add the dependency to them. - applyDefiningSharedLibraryUpdateLocked(pkg, libInfo, SharedLibraryInfo::addDependency); - if (pkgSetting != null) { - usesLibraryFiles.addAll(pkgSetting.getPkgState().getUsesLibraryFiles()); - } - } - } - - @GuardedBy("mLock") - void updateSharedLibrariesLocked(AndroidPackage pkg, PackageSetting pkgSetting, - @Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting, - Map<String, AndroidPackage> availablePackages) - throws PackageManagerException { - final ArrayList<SharedLibraryInfo> sharedLibraryInfos = - SharedLibraryHelper.collectSharedLibraryInfos( - pkgSetting.getPkg(), availablePackages, mSharedLibraries, - null /* newLibraries */, mInjector.getCompatibility()); - executeSharedLibrariesUpdateLPr(pkg, pkgSetting, changingLib, changingLibSetting, - sharedLibraryInfos, mUserManager.getUserIds()); - } - - void executeSharedLibrariesUpdateLPr(AndroidPackage pkg, - @NonNull PackageSetting pkgSetting, @Nullable AndroidPackage changingLib, - @Nullable PackageSetting changingLibSetting, - ArrayList<SharedLibraryInfo> usesLibraryInfos, int[] allUsers) { - // If the package provides libraries, clear their old dependencies. - // This method will set them up again. - applyDefiningSharedLibraryUpdateLocked(pkg, null, (definingLibrary, dependency) -> { - definingLibrary.clearDependencies(); - }); - if (usesLibraryInfos != null) { - pkgSetting.getPkgState().setUsesLibraryInfos(usesLibraryInfos); - // Use LinkedHashSet to preserve the order of files added to - // usesLibraryFiles while eliminating duplicates. - Set<String> usesLibraryFiles = new LinkedHashSet<>(); - for (SharedLibraryInfo libInfo : usesLibraryInfos) { - addSharedLibraryLPr(pkg, usesLibraryFiles, libInfo, changingLib, - changingLibSetting); - } - pkgSetting.setPkgStateLibraryFiles(usesLibraryFiles); - - // let's make sure we mark all static shared libraries as installed for the same users - // that its dependent packages are installed for. - int[] installedUsers = new int[allUsers.length]; - int installedUserCount = 0; - for (int u = 0; u < allUsers.length; u++) { - if (pkgSetting.getInstalled(allUsers[u])) { - installedUsers[installedUserCount++] = allUsers[u]; - } - } - for (SharedLibraryInfo sharedLibraryInfo : usesLibraryInfos) { - if (!sharedLibraryInfo.isStatic()) { - continue; - } - final PackageSetting staticLibPkgSetting = - getPackageSettingForMutation(sharedLibraryInfo.getPackageName()); - if (staticLibPkgSetting == null) { - Slog.wtf(TAG, "Shared lib without setting: " + sharedLibraryInfo); - continue; - } - for (int u = 0; u < installedUserCount; u++) { - staticLibPkgSetting.setInstalled(true, installedUsers[u]); - } - } - } else { - pkgSetting.getPkgState().setUsesLibraryInfos(Collections.emptyList()) - .setUsesLibraryFiles(Collections.emptyList()); - } - } - - private static boolean hasString(List<String> list, List<String> which) { - if (list == null || which == null) { - return false; - } - for (int i=list.size()-1; i>=0; i--) { - for (int j=which.size()-1; j>=0; j--) { - if (which.get(j).equals(list.get(i))) { - return true; - } - } - } - return false; - } - - @GuardedBy("mLock") - ArrayList<AndroidPackage> updateAllSharedLibrariesLocked( - @Nullable AndroidPackage updatedPkg, @Nullable PackageSetting updatedPkgSetting, - Map<String, AndroidPackage> availablePackages) { - ArrayList<AndroidPackage> resultList = null; - // Set of all descendants of a library; used to eliminate cycles - ArraySet<String> descendants = null; - // The current list of packages that need updating - List<Pair<AndroidPackage, PackageSetting>> needsUpdating = null; - if (updatedPkg != null && updatedPkgSetting != null) { - needsUpdating = new ArrayList<>(1); - needsUpdating.add(Pair.create(updatedPkg, updatedPkgSetting)); - } - do { - final Pair<AndroidPackage, PackageSetting> changingPkgPair = - (needsUpdating == null) ? null : needsUpdating.remove(0); - final AndroidPackage changingPkg = changingPkgPair != null - ? changingPkgPair.first : null; - final PackageSetting changingPkgSetting = changingPkgPair != null - ? changingPkgPair.second : null; - for (int i = mPackages.size() - 1; i >= 0; --i) { - final AndroidPackage pkg = mPackages.valueAt(i); - final PackageSetting pkgSetting = mSettings.getPackageLPr(pkg.getPackageName()); - if (changingPkg != null - && !hasString(pkg.getUsesLibraries(), changingPkg.getLibraryNames()) - && !hasString(pkg.getUsesOptionalLibraries(), changingPkg.getLibraryNames()) - && !ArrayUtils.contains(pkg.getUsesStaticLibraries(), - changingPkg.getStaticSharedLibName()) - && !ArrayUtils.contains(pkg.getUsesSdkLibraries(), - changingPkg.getSdkLibName())) { - continue; - } - if (resultList == null) { - resultList = new ArrayList<>(); - } - resultList.add(pkg); - // if we're updating a shared library, all of its descendants must be updated - if (changingPkg != null) { - if (descendants == null) { - descendants = new ArraySet<>(); - } - if (!descendants.contains(pkg.getPackageName())) { - descendants.add(pkg.getPackageName()); - needsUpdating.add(Pair.create(pkg, pkgSetting)); - } - } - try { - updateSharedLibrariesLocked(pkg, pkgSetting, changingPkg, - changingPkgSetting, availablePackages); - } catch (PackageManagerException e) { - // If a system app update or an app and a required lib missing we - // delete the package and for updated system apps keep the data as - // it is better for the user to reinstall than to be in an limbo - // state. Also libs disappearing under an app should never happen - // - just in case. - if (!pkg.isSystem() || pkgSetting.getPkgState().isUpdatedSystemApp()) { - final int flags = pkgSetting.getPkgState().isUpdatedSystemApp() - ? PackageManager.DELETE_KEEP_DATA : 0; - mDeletePackageHelper.deletePackageLIF(pkg.getPackageName(), null, true, - mUserManager.getUserIds(), flags, null, - true); - } - Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); - } - } - } while (needsUpdating != null && needsUpdating.size() > 0); - return resultList; - } - - @GuardedBy("mLock") - private void addBuiltInSharedLibraryLocked(SystemConfig.SharedLibraryEntry entry) { - if (nonStaticSharedLibExistsLocked(entry.name)) { - return; - } - - SharedLibraryInfo libraryInfo = new SharedLibraryInfo(entry.filename, null, null, - entry.name, SharedLibraryInfo.VERSION_UNDEFINED, - SharedLibraryInfo.TYPE_BUILTIN, - new VersionedPackage(PLATFORM_PACKAGE_NAME, (long)0), null, null, - entry.isNative); - - commitSharedLibraryInfoLocked(libraryInfo); - } - - @GuardedBy("mLock") - private boolean nonStaticSharedLibExistsLocked(String name) { - return SharedLibraryHelper.sharedLibExists(name, SharedLibraryInfo.VERSION_UNDEFINED, - mSharedLibraries); - } - - @GuardedBy("mLock") - void commitSharedLibraryInfoLocked(SharedLibraryInfo libraryInfo) { - final String name = libraryInfo.getName(); - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name); - if (versionedLib == null) { - versionedLib = new WatchedLongSparseArray<>(); - mSharedLibraries.put(name, versionedLib); - } - final String declaringPackageName = libraryInfo.getDeclaringPackage().getPackageName(); - if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) { - mStaticLibsByDeclaringPackage.put(declaringPackageName, versionedLib); - } - versionedLib.put(libraryInfo.getLongVersion(), libraryInfo); - } - @Override public Property getProperty(String propertyName, String packageName, String className) { Objects.requireNonNull(propertyName); @@ -4926,8 +4570,8 @@ public class PackageManagerService extends IPackageManager.Stub if (pus.isSuspended()) { for (int i = 0; i < pus.getSuspendParams().size(); i++) { final SuspendParams params = pus.getSuspendParams().valueAt(i); - if (params != null && params.appExtras != null) { - allExtras.putAll(params.appExtras); + if (params != null && params.getAppExtras() != null) { + allExtras.putAll(params.getAppExtras()); } } } @@ -5000,9 +4644,9 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mLock) { for (String packageName : packagesToChange) { final PackageSetting ps = mSettings.getPackageLPr(packageName); - if (ps != null && ps.getSuspended(userId)) { + if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) { ps.removeSuspension(suspendingPackagePredicate, userId); - if (!ps.getSuspended(userId)) { + if (!ps.getUserStateOrDefault(userId).isSuspended()) { unsuspendedPackages.add(ps.getPackageName()); unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId())); } @@ -7786,8 +7430,8 @@ public class PackageManagerService extends IPackageManager.Stub if (userState.isSuspended()) { for (int i = 0; i < userState.getSuspendParams().size(); i++) { final SuspendParams params = userState.getSuspendParams().valueAt(i); - if (params != null && params.launcherExtras != null) { - allExtras.putAll(params.launcherExtras); + if (params != null && params.getLauncherExtras() != null) { + allExtras.putAll(params.getLauncherExtras()); } } } @@ -7876,7 +7520,7 @@ public class PackageManagerService extends IPackageManager.Stub } final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage); - return (suspendParams != null) ? suspendParams.dialogInfo : null; + return (suspendParams != null) ? suspendParams.getDialogInfo() : null; } @Override @@ -8322,7 +7966,13 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> actionLocked, @UserIdInt int userId) { - PackageManagerService.this.forEachInstalledPackage(actionLocked, userId); + forEachInstalledPackage(true, actionLocked, userId); + } + + @Override + public void forEachInstalledPackage(boolean locked, + @NonNull Consumer<AndroidPackage> action, int userId) { + PackageManagerService.this.forEachInstalledPackage(locked, action, userId); } @Override @@ -8595,51 +8245,24 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void withPackageSettingsSnapshot( @NonNull Consumer<Function<String, PackageStateInternal>> block) { - final Computer snapshot = snapshotComputer(); - - // This method needs to either lock or not lock consistently throughout the method, - // so if the live computer is returned, force a wrapping sync block. - if (snapshot == mLiveComputer) { - synchronized (mLock) { - block.accept(snapshot::getPackageStateInternal); - } - } else { - block.accept(snapshot::getPackageStateInternal); - } + executeWithConsistentComputer(computer -> + block.accept(computer::getPackageStateInternal)); } @Override public <Output> Output withPackageSettingsSnapshotReturning( @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>, Output> block) { - final Computer snapshot = snapshotComputer(); - - // This method needs to either lock or not lock consistently throughout the method, - // so if the live computer is returned, force a wrapping sync block. - if (snapshot == mLiveComputer) { - synchronized (mLock) { - return block.apply(snapshot::getPackageStateInternal); - } - } else { - return block.apply(snapshot::getPackageStateInternal); - } + return executeWithConsistentComputerReturning(computer -> + block.apply(computer::getPackageStateInternal)); } @Override public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing( @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String, PackageStateInternal>, ExceptionType> block) throws ExceptionType { - final Computer snapshot = snapshotComputer(); - - // This method needs to either lock or not lock consistently throughout the method, - // so if the live computer is returned, force a wrapping sync block. - if (snapshot == mLiveComputer) { - synchronized (mLock) { - block.accept(snapshot::getPackageStateInternal); - } - } else { - block.accept(snapshot::getPackageStateInternal); - } + executeWithConsistentComputerThrowing(computer -> + block.accept(computer::getPackageStateInternal)); } @Override @@ -8649,17 +8272,9 @@ public class PackageManagerService extends IPackageManager.Stub Function<String, PackageStateInternal>, ExceptionOne, ExceptionTwo> block) throws ExceptionOne, ExceptionTwo { - final Computer snapshot = snapshotComputer(); - - // This method needs to either lock or not lock consistently throughout the method, - // so if the live computer is returned, force a wrapping sync block. - if (snapshot == mLiveComputer) { - synchronized (mLock) { - block.accept(snapshot::getPackageStateInternal); - } - } else { - block.accept(snapshot::getPackageStateInternal); - } + executeWithConsistentComputerThrowing2( + (FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne, + ExceptionTwo>) computer -> block.accept(computer::getPackageStateInternal)); } @Override @@ -8669,17 +8284,8 @@ public class PackageManagerService extends IPackageManager.Stub Function<String, PackageStateInternal>, Output, ExceptionType> block) throws ExceptionType { - final Computer snapshot = snapshotComputer(); - - // This method needs to either lock or not lock consistently throughout the method, - // so if the live computer is returned, force a wrapping sync block. - if (snapshot == mLiveComputer) { - synchronized (mLock) { - return block.apply(snapshot::getPackageStateInternal); - } - } else { - return block.apply(snapshot::getPackageStateInternal); - } + return executeWithConsistentComputerReturningThrowing(computer -> + block.apply(computer::getPackageStateInternal)); } @Override @@ -8688,6 +8294,20 @@ public class PackageManagerService extends IPackageManager.Stub PackageManagerService.this.mAppDataHelper.reconcileAppsData(userId, flags, migrateAppsData); } + + @NonNull + @Override + public PackageStateMutator.InitialState recordInitialState() { + return PackageManagerService.this.recordInitialState(); + } + + @Nullable + @Override + public PackageStateMutator.Result commitPackageStateMutation( + @Nullable PackageStateMutator.InitialState state, + @NonNull Consumer<PackageStateMutator> consumer) { + return PackageManagerService.this.commitPackageStateMutation(state, consumer); + } } @Override @@ -8730,7 +8350,15 @@ public class PackageManagerService extends IPackageManager.Stub @Nullable @GuardedBy("mLock") PackageSetting getPackageSettingForMutation(String packageName) { - return (PackageSetting) mComputer.getPackageStateInternal(packageName); + return mSettings.getPackageLPr(packageName); + } + + // TODO: Remove + @Deprecated + @Nullable + @GuardedBy("mLock") + PackageSetting getDisabledPackageSettingForMutation(String packageName) { + return mSettings.getDisabledSystemPkgLPr(packageName); } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -8738,7 +8366,7 @@ public class PackageManagerService extends IPackageManager.Stub PackageStateInternal getPackageStateInternal(String packageName) { Computer computer = snapshotComputer(); if (computer == mLiveComputer) { - synchronized (mLock) { + synchronized (mLiveComputerSyncLock) { PackageSetting pkgSetting = (PackageSetting) computer.getPackageStateInternal(packageName); if (pkgSetting == null) { @@ -8747,15 +8375,16 @@ public class PackageManagerService extends IPackageManager.Stub return new PackageSetting(pkgSetting); } + } else { + return computer.getPackageStateInternal(packageName); } - return computer.getPackageStateInternal(packageName); } @Nullable PackageStateInternal getPackageStateInternal(String packageName, int callingUid) { Computer computer = snapshotComputer(); if (computer == mLiveComputer) { - synchronized (mLock) { + synchronized (mLiveComputerSyncLock) { PackageSetting pkgSetting = (PackageSetting) computer.getPackageStateInternal(packageName, callingUid); if (pkgSetting == null) { @@ -8764,8 +8393,9 @@ public class PackageManagerService extends IPackageManager.Stub return new PackageSetting(pkgSetting); } + } else { + return computer.getPackageStateInternal(packageName, callingUid); } - return computer.getPackageStateInternal(packageName, callingUid); } @Nullable @@ -8773,7 +8403,7 @@ public class PackageManagerService extends IPackageManager.Stub int callingUid, @UserIdInt int userId) { Computer computer = snapshotComputer(); if (computer == mLiveComputer) { - synchronized (mLock) { + synchronized (mLiveComputerSyncLock) { PackageSetting pkgSetting = (PackageSetting) filterPackageStateForInstalledAndFiltered(computer, packageName, callingUid, userId); @@ -8782,9 +8412,10 @@ public class PackageManagerService extends IPackageManager.Stub } return new PackageSetting(pkgSetting); } + } else { + return filterPackageStateForInstalledAndFiltered(computer, packageName, callingUid, + userId); } - - return filterPackageStateForInstalledAndFiltered(computer, packageName, callingUid, userId); } @Nullable @@ -8812,16 +8443,8 @@ public class PackageManagerService extends IPackageManager.Stub Computer computer = snapshotComputer(); if (computer == mLiveComputer) { return new ArrayMap<>(computer.getPackageStates()); - } - return computer.getPackageStates(); - } - - void forEachPackage(Consumer<AndroidPackage> actionLocked) { - synchronized (mLock) { - int numPackages = mPackages.size(); - for (int i = 0; i < numPackages; i++) { - actionLocked.accept(mPackages.valueAt(i)); - } + } else { + return computer.getPackageStates(); } } @@ -8834,17 +8457,31 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) { + void forEachPackageState(boolean locked, Consumer<PackageStateInternal> action) { if (locked) { - forEachPackageSetting(action::accept); + synchronized (mLiveComputerSyncLock) { + forEachPackageState(mComputer.getPackageStates(), action); + } } else { Computer computer = snapshotComputer(); if (computer == mLiveComputer) { - synchronized (mLock) { + synchronized (mLiveComputerSyncLock) { forEachPackageState(computer.getPackageStates(), action); - }; + } + } else { + forEachPackageState(computer.getPackageStates(), action); } - forEachPackageState(computer.getPackageStates(), action); + } + } + + void forEachPackage(Consumer<AndroidPackage> action) { + Computer computer = snapshotComputer(); + if (computer == mLiveComputer) { + synchronized (mLiveComputerSyncLock) { + forEachPackage(computer.getPackageStates(), action); + } + } else { + forEachPackage(computer.getPackageStates(), action); } } @@ -8858,18 +8495,103 @@ public class PackageManagerService extends IPackageManager.Stub } } - void forEachInstalledPackage(@NonNull Consumer<AndroidPackage> actionLocked, + private void forEachPackage( + @NonNull ArrayMap<String, ? extends PackageStateInternal> packageStates, + @NonNull Consumer<AndroidPackage> consumer) { + int size = packageStates.size(); + for (int index = 0; index < size; index++) { + PackageStateInternal packageState = packageStates.valueAt(index); + if (packageState.getPkg() != null) { + consumer.accept(packageState.getPkg()); + } + } + } + + void forEachInstalledPackage(boolean locked, @NonNull Consumer<AndroidPackage> action, @UserIdInt int userId) { - synchronized (mLock) { - int numPackages = mPackages.size(); - for (int i = 0; i < numPackages; i++) { - AndroidPackage pkg = mPackages.valueAt(i); - PackageSetting setting = mSettings.getPackageLPr(pkg.getPackageName()); - if (setting == null || !setting.getInstalled(userId)) { - continue; + Consumer<PackageStateInternal> actionWrapped = packageState -> { + if (packageState.getPkg() != null + && packageState.getUserStateOrDefault(userId).isInstalled()) { + action.accept(packageState.getPkg()); + } + }; + if (locked) { + synchronized (mLiveComputerSyncLock) { + forEachPackageState(mComputer.getPackageStates(), actionWrapped); + } + } else { + Computer computer = snapshotComputer(); + if (computer == mLiveComputer) { + synchronized (mLiveComputerSyncLock) { + forEachPackageState(computer.getPackageStates(), actionWrapped); } - actionLocked.accept(pkg); + } else { + forEachPackageState(computer.getPackageStates(), actionWrapped); + } + } + } + + private void executeWithConsistentComputer( + @NonNull FunctionalUtils.ThrowingConsumer<Computer> consumer) { + Computer computer = snapshotComputer(); + if (computer == mLiveComputer) { + synchronized (mLiveComputerSyncLock) { + consumer.accept(computer); + } + } else { + consumer.accept(computer); + } + } + + private <T> T executeWithConsistentComputerReturning( + @NonNull FunctionalUtils.ThrowingFunction<Computer, T> function) { + Computer computer = snapshotComputer(); + if (computer == mLiveComputer) { + synchronized (mLiveComputerSyncLock) { + return function.apply(computer); + } + } else { + return function.apply(computer); + } + } + + private <ExceptionType extends Exception> void executeWithConsistentComputerThrowing( + @NonNull FunctionalUtils.ThrowingCheckedConsumer<Computer, ExceptionType> consumer) + throws ExceptionType { + Computer computer = snapshotComputer(); + if (computer == mLiveComputer) { + synchronized (mLiveComputerSyncLock) { + consumer.accept(computer); + } + } else { + consumer.accept(computer); + } + } + + private <ExceptionOne extends Exception, ExceptionTwo extends Exception> void + executeWithConsistentComputerThrowing2( + @NonNull FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne, + ExceptionTwo> consumer) throws ExceptionOne, ExceptionTwo { + Computer computer = snapshotComputer(); + if (computer == mLiveComputer) { + synchronized (mLiveComputerSyncLock) { + consumer.accept(computer); + } + } else { + consumer.accept(computer); + } + } + + private <T, ExceptionType extends Exception> T executeWithConsistentComputerReturningThrowing( + @NonNull FunctionalUtils.ThrowingCheckedFunction<Computer, T, ExceptionType> function) + throws ExceptionType { + Computer computer = snapshotComputer(); + if (computer == mLiveComputer) { + synchronized (mLiveComputerSyncLock) { + return function.apply(computer); } + } else { + return function.apply(computer); } } @@ -9068,7 +8790,8 @@ public class PackageManagerService extends IPackageManager.Stub enforceOwnerRights(packageName, Binder.getCallingUid()); final boolean changed; synchronized (mLock) { - changed = mSettings.getPackageLPr(packageName).setMimeGroup(mimeGroup, mimeTypes); + changed = mSettings.getPackageLPr(packageName).setMimeGroup(mimeGroup, + new ArraySet<>(mimeTypes)); } if (changed) { applyMimeGroupChanges(packageName, mimeGroup); @@ -9571,4 +9294,62 @@ public class PackageManagerService extends IPackageManager.Stub return new Pair<>(rescanFlags, reparseFlags); } + + /** + * @see PackageManagerInternal#recordInitialState() + */ + @NonNull + public PackageStateMutator.InitialState recordInitialState() { + return mPackageStateMutator.initialState(mChangedPackagesSequenceNumber); + } + + /** + * @see PackageManagerInternal#commitPackageStateMutation(PackageStateMutator.InitialState, + * Consumer) + */ + @NonNull + public PackageStateMutator.Result commitPackageStateMutation( + @Nullable PackageStateMutator.InitialState initialState, + @NonNull Consumer<PackageStateMutator> consumer) { + synchronized (mPackageStateWriteLock) { + final PackageStateMutator.Result result = mPackageStateMutator.generateResult( + initialState, mChangedPackagesSequenceNumber); + if (result != PackageStateMutator.Result.SUCCESS) { + return result; + } + + consumer.accept(mPackageStateMutator); + onChanged(); + } + + return PackageStateMutator.Result.SUCCESS; + } + + /** + * @see PackageManagerInternal#commitPackageStateMutation(PackageStateMutator.InitialState, + * Consumer) + */ + @NonNull + public PackageStateMutator.Result commitPackageStateMutation( + @Nullable PackageStateMutator.InitialState initialState, @NonNull String packageName, + @NonNull Consumer<PackageStateWrite> consumer) { + synchronized (mPackageStateWriteLock) { + final PackageStateMutator.Result result = mPackageStateMutator.generateResult( + initialState, mChangedPackagesSequenceNumber); + if (result != PackageStateMutator.Result.SUCCESS) { + return result; + } + + PackageStateWrite state = mPackageStateMutator.forPackage(packageName); + if (state == null) { + return PackageStateMutator.Result.SPECIFIC_PACKAGE_NULL; + } else { + consumer.accept(state); + } + + onChanged(); + } + + return PackageStateMutator.Result.SUCCESS; + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index d14cc1f3ebd9..05bb01eb4959 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -137,6 +137,7 @@ public class PackageManagerServiceInjector { private final Singleton<Handler> mHandlerProducer; private final Singleton<BackgroundDexOptService> mBackgroundDexOptService; private final Singleton<IBackupManager> mIBackupManager; + private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer; PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock, Installer installer, Object installLock, PackageAbiHelper abiHelper, @@ -173,7 +174,8 @@ public class PackageManagerServiceInjector { ServiceProducer getLocalServiceProducer, ServiceProducer getSystemServiceProducer, Producer<BackgroundDexOptService> backgroundDexOptService, - Producer<IBackupManager> iBackupManager) { + Producer<IBackupManager> iBackupManager, + Producer<SharedLibrariesImpl> sharedLibrariesProducer) { mContext = context; mLock = lock; mInstaller = installer; @@ -224,6 +226,7 @@ public class PackageManagerServiceInjector { mHandlerProducer = new Singleton<>(handlerProducer); mBackgroundDexOptService = new Singleton<>(backgroundDexOptService); mIBackupManager = new Singleton<>(iBackupManager); + mSharedLibrariesProducer = new Singleton<>(sharedLibrariesProducer); } /** @@ -392,6 +395,10 @@ public class PackageManagerServiceInjector { return mIBackupManager.get(this, mPackageManager); } + public SharedLibrariesImpl getSharedLibrariesImpl() { + return mSharedLibrariesProducer.get(this, mPackageManager); + } + /** Provides an abstraction to static access to system state. */ public interface SystemWrapper { void disablePackageCaches(); diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index 4f21d0e8d1c0..a532fe3a3d4d 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -45,6 +45,7 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageHelper; import com.android.server.LocalServices; +import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.rollback.RollbackManagerInternal; @@ -99,9 +100,11 @@ final class PackageSessionVerifier { storeSession(session.mStagedSession); if (session.isMultiPackage()) { for (PackageInstallerSession child : session.getChildSessions()) { + checkApexUpdateAllowed(child); checkRebootlessApex(child); } } else { + checkApexUpdateAllowed(session); checkRebootlessApex(session); } verifyAPK(session, callback); @@ -203,7 +206,7 @@ final class PackageSessionVerifier { } private void onVerificationFailure(StagingManager.StagedSession session, Callback callback, - @SessionInfo.StagedSessionErrorCode int errorCode, String errorMessage) { + @SessionInfo.SessionErrorCode int errorCode, String errorMessage) { if (!ensureActiveApexSessionIsAborted(session)) { Slog.e(TAG, "Failed to abort apex session " + session.sessionId()); // Safe to ignore active apex session abortion failure since session will be marked @@ -461,6 +464,51 @@ final class PackageSessionVerifier { return mApexManager.abortStagedSession(sessionId); } + private boolean isApexUpdateAllowed(String apexPackageName, String installerPackageName) { + if (mPm.getModuleInfo(apexPackageName, 0) != null) { + final String modulesInstaller = + SystemConfig.getInstance().getModulesInstallerPackageName(); + if (modulesInstaller == null) { + Slog.w(TAG, "No modules installer defined"); + return false; + } + return modulesInstaller.equals(installerPackageName); + } + final String vendorApexInstaller = + SystemConfig.getInstance().getAllowedVendorApexes().get(apexPackageName); + if (vendorApexInstaller == null) { + Slog.w(TAG, apexPackageName + " is not allowed to be updated"); + return false; + } + return vendorApexInstaller.equals(installerPackageName); + } + + /** + * Checks if APEX update is allowed. + * + * This phase is shared between staged and non-staged sessions and should be called after + * boot is completed since this check depends on the ModuleInfoProvider, which is only populated + * after device has booted. + */ + private void checkApexUpdateAllowed(PackageInstallerSession session) + throws PackageManagerException { + if (!session.isApexSession()) { + return; + } + final int installFlags = session.params.installFlags; + if ((installFlags & PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK) != 0) { + return; + } + final String packageName = session.getPackageName(); + final String installerPackageName = session.getInstallSource().installerPackageName; + if (!isApexUpdateAllowed(packageName, installerPackageName)) { + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, + "Update of APEX package " + packageName + " is not allowed for " + + installerPackageName); + } + } + /** * Fails this rebootless APEX session if the same package name found in any staged sessions. */ diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 923a133338ab..9dbf57dd53a2 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -33,6 +33,7 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; import android.content.pm.overlay.OverlayPaths; import android.os.PersistableBundle; +import android.os.UserHandle; import android.service.pm.PackageProto; import android.util.ArrayMap; import android.util.ArraySet; @@ -157,7 +158,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal private String mCpuAbiOverride; private long mLastModifiedTime; - private long firstInstallTime; private long lastUpdateTime; private long versionCode; @@ -284,7 +284,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal proto.write(PackageProto.NAME, (mRealName != null ? mRealName : mName)); proto.write(PackageProto.UID, mAppId); proto.write(PackageProto.VERSION_CODE, versionCode); - proto.write(PackageProto.INSTALL_TIME_MS, firstInstallTime); proto.write(PackageProto.UPDATE_TIME_MS, lastUpdateTime); proto.write(PackageProto.INSTALLER_NAME, installSource.installerPackageName); @@ -335,8 +334,36 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } - public PackageSetting setFirstInstallTime(long firstInstallTime) { - this.firstInstallTime = firstInstallTime; + /** + * In case of replacing an old package, restore the first install timestamps if it was installed + * for the same users + */ + public PackageSetting setFirstInstallTimeFromReplaced(PackageStateInternal replacedPkgSetting, + int[] userIds) { + for (int userId = 0; userId < userIds.length; userId++) { + final long previousFirstInstallTime = + replacedPkgSetting.getUserStateOrDefault(userId).getFirstInstallTime(); + if (previousFirstInstallTime != 0) { + modifyUserState(userId).setFirstInstallTime(previousFirstInstallTime); + } + } + onChanged(); + return this; + } + + /** + * Set the time for the first time when an app is installed for a user. If userId specifies all + * users, set the same timestamp for all the users. + */ + public PackageSetting setFirstInstallTime(long firstInstallTime, int userId) { + if (userId == UserHandle.USER_ALL) { + int userStateCount = mUserStates.size(); + for (int i = 0; i < userStateCount; i++) { + mUserStates.valueAt(i).setFirstInstallTime(firstInstallTime); + } + } else { + modifyUserState(userId).setFirstInstallTime(firstInstallTime); + } onChanged(); return this; } @@ -395,14 +422,13 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } - public boolean setMimeGroup(String mimeGroup, List<String> mimeTypes) { + public boolean setMimeGroup(String mimeGroup, ArraySet<String> newMimeTypes) { Set<String> oldMimeTypes = mimeGroups == null ? null : mimeGroups.get(mimeGroup); if (oldMimeTypes == null) { throw new IllegalArgumentException("Unknown MIME group " + mimeGroup + " for package " + mName); } - ArraySet<String> newMimeTypes = new ArraySet<>(mimeTypes); boolean hasChanges = !newMimeTypes.equals(oldMimeTypes); mimeGroups.put(mimeGroup, newMimeTypes); if (hasChanges) { @@ -613,7 +639,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal mSecondaryCpuAbi = other.mSecondaryCpuAbi; mCpuAbiOverride = other.mCpuAbiOverride; mLastModifiedTime = other.mLastModifiedTime; - firstInstallTime = other.firstInstallTime; lastUpdateTime = other.lastUpdateTime; versionCode = other.versionCode; signatures = other.signatures; @@ -670,6 +695,15 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return state; } + public PackageUserStateImpl getOrCreateUserState(@UserIdInt int userId) { + PackageUserStateImpl state = mUserStates.get(userId); + if (state == null) { + state = new PackageUserStateImpl(); + mUserStates.put(userId, state); + } + return state; + } + @NonNull public PackageUserStateInternal readUserState(int userId) { PackageUserStateInternal state = mUserStates.get(userId); @@ -832,7 +866,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } final SuspendParams oldSuspendParams = existingUserState.getSuspendParams().put(suspendingPackage, newSuspendParams); - existingUserState.setSuspended(true); onChanged(); return !Objects.equals(oldSuspendParams, newSuspendParams); } @@ -848,7 +881,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal existingUserState.setSuspendParams(null); } } - existingUserState.setSuspended((existingUserState.getSuspendParams() != null)); onChanged(); return wasModified; } @@ -866,7 +898,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal existingUserState.setSuspendParams(null); } } - existingUserState.setSuspended((existingUserState.getSuspendParams() != null)); onChanged(); } @@ -889,12 +920,13 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped, - boolean notLaunched, boolean hidden, int distractionFlags, boolean suspended, + boolean notLaunched, boolean hidden, int distractionFlags, ArrayMap<String, SuspendParams> suspendParams, boolean instantApp, boolean virtualPreload, String lastDisableAppCaller, ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, int installReason, int uninstallReason, - String harmfulAppWarning, String splashScreenTheme) { + String harmfulAppWarning, String splashScreenTheme, + long firstInstallTime) { modifyUserState(userId) .setSuspendParams(suspendParams) .setCeDataInode(ceDataInode) @@ -904,7 +936,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal .setNotLaunched(notLaunched) .setHidden(hidden) .setDistractionFlags(distractionFlags) - .setSuspended(suspended) .setLastDisableAppCaller(lastDisableAppCaller) .setEnabledComponents(enabledComponents) .setDisabledComponents(disabledComponents) @@ -913,22 +944,22 @@ public class PackageSetting extends SettingBase implements PackageStateInternal .setInstantApp(instantApp) .setVirtualPreload(virtualPreload) .setHarmfulAppWarning(harmfulAppWarning) - .setSplashScreenTheme(splashScreenTheme); + .setSplashScreenTheme(splashScreenTheme) + .setFirstInstallTime(firstInstallTime); onChanged(); } void setUserState(int userId, PackageUserStateInternal otherState) { setUserState(userId, otherState.getCeDataInode(), otherState.getEnabledState(), - otherState.isInstalled(), - otherState.isStopped(), otherState.isNotLaunched(), otherState.isHidden(), - otherState.getDistractionFlags(), otherState.isSuspended(), - otherState.getSuspendParams(), - otherState.isInstantApp(), + otherState.isInstalled(), otherState.isStopped(), otherState.isNotLaunched(), + otherState.isHidden(), otherState.getDistractionFlags(), + otherState.getSuspendParams(), otherState.isInstantApp(), otherState.isVirtualPreload(), otherState.getLastDisableAppCaller(), new ArraySet<>(otherState.getEnabledComponentsNoCopy()), new ArraySet<>(otherState.getDisabledComponentsNoCopy()), otherState.getInstallReason(), otherState.getUninstallReason(), - otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme()); + otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(), + otherState.getFirstInstallTime()); } ArraySet<String> getEnabledComponents(int userId) { @@ -1119,6 +1150,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal proto.write( PackageProto.UserInfoProto.LAST_DISABLED_APP_CALLER, state.getLastDisableAppCaller()); + proto.write(PackageProto.UserInfoProto.FIRST_INSTALL_TIME_MS, + state.getFirstInstallTime()); proto.end(userToken); } } @@ -1484,11 +1517,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated.Member - public long getFirstInstallTime() { - return firstInstallTime; - } - - @DataClass.Generated.Member public long getLastUpdateTime() { return lastUpdateTime; } @@ -1553,10 +1581,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated( - time = 1635870549646L, + time = 1640923794772L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int sharedUserId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.Nullable com.android.server.pm.SharedUserSetting sharedUser\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long firstInstallTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic java.util.List<java.lang.String> getMimeGroup(java.lang.String)\nprivate java.util.Set<java.lang.String> getMimeGroupInternal(java.lang.String)\npublic boolean isSharedUser()\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,java.util.List<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic int getSharedUserIdInt()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n java.lang.String getLastDisabledAppCaller(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getOverlayPathsForLibrary(int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\n boolean getSuspended(int)\n boolean addOrUpdateSuspension(java.lang.String,android.content.pm.SuspendDialogInfo,android.os.PersistableBundle,android.os.PersistableBundle,int)\n boolean removeSuspension(java.lang.String,int)\n void removeSuspension(java.util.function.Predicate<java.lang.String>,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,boolean,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n void setHarmfulAppWarning(int,java.lang.String)\n java.lang.String getHarmfulAppWarning(int)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic void setSplashScreenTheme(int,java.lang.String)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.Nullable @java.lang.Override java.lang.Integer getSharedUserId()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setSharedUser(com.android.server.pm.SharedUserSetting)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int sharedUserId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackage pkg\nprivate @android.annotation.Nullable com.android.server.pm.SharedUserSetting sharedUser\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic boolean isSharedUser()\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.parsing.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic int getSharedUserIdInt()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n java.lang.String getLastDisabledAppCaller(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getOverlayPathsForLibrary(int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\n boolean getSuspended(int)\n boolean addOrUpdateSuspension(java.lang.String,android.content.pm.SuspendDialogInfo,android.os.PersistableBundle,android.os.PersistableBundle,int)\n boolean removeSuspension(java.lang.String,int)\n void removeSuspension(java.util.function.Predicate<java.lang.String>,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n android.util.ArraySet<java.lang.String> getEnabledComponents(int)\n android.util.ArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponents(android.util.ArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(android.util.ArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n void setHarmfulAppWarning(int,java.lang.String)\n java.lang.String getHarmfulAppWarning(int)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic void setSplashScreenTheme(int,java.lang.String)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackageApi getAndroidPackage()\npublic @android.annotation.Nullable @java.lang.Override java.lang.Integer getSharedUserId()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.SharedLibraryInfo> getUsesLibraryInfos()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setSharedUser(com.android.server.pm.SharedUserSetting)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 749495c5a5b4..d60d01971534 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -29,10 +29,7 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.content.pm.PackageManager; -import android.content.pm.SharedLibraryInfo; -import android.content.pm.VersionedPackage; import android.content.pm.parsing.component.ParsedInstrumentation; -import android.os.Process; import android.os.UserHandle; import android.os.incremental.IncrementalManager; import android.util.Log; @@ -46,7 +43,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.utils.WatchedLongSparseArray; import java.io.File; import java.util.Collections; @@ -62,6 +58,7 @@ final class RemovePackageHelper { private final Installer mInstaller; private final UserManagerInternal mUserManagerInternal; private final PermissionManagerServiceInternal mPermissionManager; + private final SharedLibrariesImpl mSharedLibraries; private final AppDataHelper mAppDataHelper; // TODO(b/198166813): remove PMS dependency @@ -71,6 +68,7 @@ final class RemovePackageHelper { mInstaller = mPm.mInjector.getInstaller(); mUserManagerInternal = mPm.mInjector.getUserManagerInternal(); mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal(); + mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl(); mAppDataHelper = appDataHelper; } @@ -174,7 +172,7 @@ final class RemovePackageHelper { final int libraryNamesSize = pkg.getLibraryNames().size(); for (i = 0; i < libraryNamesSize; i++) { String name = pkg.getLibraryNames().get(i); - if (removeSharedLibraryLPw(name, 0)) { + if (mSharedLibraries.removeSharedLibraryLPw(name, 0)) { if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); @@ -191,7 +189,8 @@ final class RemovePackageHelper { // Any package can hold SDK or static shared libraries. if (pkg.getSdkLibName() != null) { - if (removeSharedLibraryLPw(pkg.getSdkLibName(), pkg.getSdkLibVersionMajor())) { + if (mSharedLibraries.removeSharedLibraryLPw( + pkg.getSdkLibName(), pkg.getSdkLibVersionMajor())) { if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); @@ -203,7 +202,7 @@ final class RemovePackageHelper { } } if (pkg.getStaticSharedLibName() != null) { - if (removeSharedLibraryLPw(pkg.getStaticSharedLibName(), + if (mSharedLibraries.removeSharedLibraryLPw(pkg.getStaticSharedLibName(), pkg.getStaticSharedLibVersion())) { if (DEBUG_REMOVE && chatty) { if (r == null) { @@ -221,44 +220,6 @@ final class RemovePackageHelper { } } - private boolean removeSharedLibraryLPw(String name, long version) { - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get(name); - if (versionedLib == null) { - return false; - } - final int libIdx = versionedLib.indexOfKey(version); - if (libIdx < 0) { - return false; - } - SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx); - - // Remove the shared library overlays from its dependent packages. - for (int currentUserId : UserManagerService.getInstance().getUserIds()) { - final List<VersionedPackage> dependents = mPm.getPackagesUsingSharedLibrary( - libraryInfo, 0, Process.SYSTEM_UID, currentUserId); - if (dependents == null) { - continue; - } - for (VersionedPackage dependentPackage : dependents) { - final PackageSetting ps = mPm.mSettings.getPackageLPr( - dependentPackage.getPackageName()); - if (ps != null) { - ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId); - } - } - } - - versionedLib.remove(version); - if (versionedLib.size() <= 0) { - mPm.mSharedLibraries.remove(name); - if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) { - mPm.mStaticLibsByDeclaringPackage.remove(libraryInfo.getDeclaringPackage() - .getPackageName()); - } - } - return true; - } - /* * This method deletes the package from internal data structures. If the DELETE_KEEP_DATA * flag is not set, the data directory is removed as well. diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 378c9e07566e..6d2ec0da896a 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -84,6 +84,7 @@ import com.android.server.pm.parsing.library.PackageBackwardCompatibility; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.PackageStateUtils; import dalvik.system.VMRuntime; @@ -410,16 +411,19 @@ final class ScanPackageUtils { // Take care of first install / last update times. final long scanFileTime = getLastModifiedTime(parsedPackage); + final long existingFirstInstallTime = userId == UserHandle.USER_ALL + ? PackageStateUtils.getEarliestFirstInstallTime(pkgSetting.getUserStates()) + : pkgSetting.readUserState(userId).getFirstInstallTime(); if (currentTime != 0) { - if (pkgSetting.getFirstInstallTime() == 0) { - pkgSetting.setFirstInstallTime(currentTime) + if (existingFirstInstallTime == 0) { + pkgSetting.setFirstInstallTime(currentTime, userId) .setLastUpdateTime(currentTime); } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) { pkgSetting.setLastUpdateTime(currentTime); } - } else if (pkgSetting.getFirstInstallTime() == 0) { - // We need *something*. Take time time stamp of the file. - pkgSetting.setFirstInstallTime(scanFileTime) + } else if (existingFirstInstallTime == 0) { + // We need *something*. Take time stamp of the file. + pkgSetting.setFirstInstallTime(scanFileTime, userId) .setLastUpdateTime(scanFileTime); } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) { if (scanFileTime != pkgSetting.getLastModifiedTime()) { diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index ed85ff9fa291..4345d5136fb9 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.permission.LegacyPermissionState; +import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.utils.Snappable; import com.android.server.utils.Watchable; import com.android.server.utils.WatchableImpl; @@ -88,6 +89,7 @@ public abstract class SettingBase implements Watchable, Snappable { * Notify listeners that this object has changed. */ protected void onChanged() { + PackageStateMutator.onPackageStateChanged(); dispatchChange(this); } @@ -122,7 +124,7 @@ public abstract class SettingBase implements Watchable, Snappable { return mLegacyPermissionsState; } - SettingBase setFlags(int pkgFlags) { + public SettingBase setFlags(int pkgFlags) { this.mPkgFlags = pkgFlags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_EXTERNAL_STORAGE @@ -131,7 +133,7 @@ public abstract class SettingBase implements Watchable, Snappable { return this; } - SettingBase setPrivateFlags(int pkgPrivateFlags) { + public SettingBase setPrivateFlags(int pkgPrivateFlags) { this.mPkgPrivateFlags = pkgPrivateFlags & (ApplicationInfo.PRIVATE_FLAG_PRIVILEGED | ApplicationInfo.PRIVATE_FLAG_OEM diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index b1ce6a2a5366..21df5a9c1a45 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -114,6 +114,7 @@ import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.permission.LegacyPermissionState.PermissionState; +import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SuspendParams; @@ -162,6 +163,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; /** * Holds information about dynamic settings. @@ -257,7 +259,7 @@ public final class Settings implements Watchable, Snappable { public static final int SIGNATURE_MALFORMED_RECOVER = 3; } - private static final boolean DEBUG_STOPPED = false; + static final boolean DEBUG_STOPPED = false; private static final boolean DEBUG_MU = false; private static final boolean DEBUG_KERNEL = false; private static final boolean DEBUG_PARSER = false; @@ -353,6 +355,7 @@ public final class Settings implements Watchable, Snappable { private static final String ATTR_SDK_VERSION = "sdkVersion"; private static final String ATTR_DATABASE_VERSION = "databaseVersion"; private static final String ATTR_VALUE = "value"; + private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time"; private final PackageManagerTracedLock mLock; @@ -625,7 +628,15 @@ public final class Settings implements Watchable, Snappable { mOtherAppIds = new WatchedSparseArray<>(); mPermissions = new LegacyPermissionSettings(lock); mRuntimePermissionsPersistence = new RuntimePermissionPersistence( - runtimePermissionsPersistence); + runtimePermissionsPersistence, new Consumer<Integer>() { + @Override + public void accept(Integer userId) { + synchronized (mLock) { + mRuntimePermissionsPersistence.writeStateForUserSync(userId, + mPermissionDataProvider, mPackages, mSharedUsers); + } + } + }); mPermissionDataProvider = permissionDataProvider; mSystemDir = new File(dataDir, "system"); @@ -989,7 +1000,6 @@ public final class Settings implements Watchable, Snappable { true /*notLaunched*/, false /*hidden*/, 0 /*distractionFlags*/, - false /*suspended*/, null /*suspendParams*/, instantApp, virtualPreload, @@ -998,8 +1008,9 @@ public final class Settings implements Watchable, Snappable { null /*disabledComponents*/, PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.UNINSTALL_REASON_UNKNOWN, - null, /*harmfulAppWarning*/ - null /*splashscreenTheme*/ + null /*harmfulAppWarning*/, + null /*splashscreenTheme*/, + 0 /*firstInstallTime*/ ); } } @@ -1423,7 +1434,7 @@ public final class Settings implements Watchable, Snappable { void writeAllRuntimePermissionsLPr() { for (int userId : UserManagerService.getInstance().getUserIds()) { - mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId); + mRuntimePermissionsPersistence.writeStateForUserAsync(userId); } } @@ -1432,15 +1443,15 @@ public final class Settings implements Watchable, Snappable { } void updateRuntimePermissionsFingerprintLPr(@UserIdInt int userId) { - mRuntimePermissionsPersistence.updateRuntimePermissionsFingerprintLPr(userId); + mRuntimePermissionsPersistence.updateRuntimePermissionsFingerprint(userId); } int getDefaultRuntimePermissionsVersionLPr(int userId) { - return mRuntimePermissionsPersistence.getVersionLPr(userId); + return mRuntimePermissionsPersistence.getVersion(userId); } void setDefaultRuntimePermissionsVersionLPr(int version, int userId) { - mRuntimePermissionsPersistence.setVersionLPr(version, userId); + mRuntimePermissionsPersistence.setVersion(version, userId); } void setPermissionControllerVersion(long version) { @@ -1602,7 +1613,8 @@ public final class Settings implements Watchable, Snappable { } } - void readPackageRestrictionsLPr(int userId) { + void readPackageRestrictionsLPr(int userId, + @NonNull ArrayMap<String, Long> origFirstInstallTimes) { if (DEBUG_MU) { Log.i(TAG, "Reading package restrictions for user=" + userId); } @@ -1646,7 +1658,6 @@ public final class Settings implements Watchable, Snappable { false /*notLaunched*/, false /*hidden*/, 0 /*distractionFlags*/, - false /*suspended*/, null /*suspendParams*/, false /*instantApp*/, false /*virtualPreload*/, @@ -1656,7 +1667,9 @@ public final class Settings implements Watchable, Snappable { PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, - null /* splashScreenTheme*/); + null /* splashScreenTheme*/, + 0 /*firstInstallTime*/ + ); } return; } @@ -1746,6 +1759,8 @@ public final class Settings implements Watchable, Snappable { PackageManager.UNINSTALL_REASON_UNKNOWN); final String splashScreenTheme = parser.getAttributeValue(null, ATTR_SPLASH_SCREEN_THEME); + final long firstInstallTime = parser.getAttributeLongHex(null, + ATTR_FIRST_INSTALL_TIME, 0); ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; @@ -1816,10 +1831,11 @@ public final class Settings implements Watchable, Snappable { setBlockUninstallLPw(userId, name, true); } ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched, - hidden, distractionFlags, suspended, suspendParamsMap, - instantApp, virtualPreload, enabledCaller, enabledComponents, - disabledComponents, installReason, uninstallReason, harmfulAppWarning, - splashScreenTheme); + hidden, distractionFlags, suspendParamsMap, instantApp, virtualPreload, + enabledCaller, enabledComponents, disabledComponents, installReason, + uninstallReason, harmfulAppWarning, splashScreenTheme, + firstInstallTime != 0 ? firstInstallTime : + origFirstInstallTimes.getOrDefault(name, 0L)); mDomainVerificationManager.setLegacyUserState(name, userId, verifState); } else if (tagName.equals("preferred-activities")) { @@ -2072,6 +2088,8 @@ public final class Settings implements Watchable, Snappable { serializer.attributeInt(null, ATTR_INSTALL_REASON, ustate.getInstallReason()); } + serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME, + ustate.getFirstInstallTime()); if (ustate.getUninstallReason() != PackageManager.UNINSTALL_REASON_UNKNOWN) { serializer.attributeInt(null, ATTR_UNINSTALL_REASON, ustate.getUninstallReason()); @@ -2739,7 +2757,6 @@ public final class Settings implements Watchable, Snappable { } serializer.attribute(null, "codePath", pkg.getPathString()); serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime()); - serializer.attributeLongHex(null, "it", pkg.getFirstInstallTime()); serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime()); serializer.attributeLong(null, "version", pkg.getVersionCode()); if (pkg.getLegacyNativeLibraryPath() != null) { @@ -2796,7 +2813,6 @@ public final class Settings implements Watchable, Snappable { serializer.attributeInt(null, "publicFlags", pkg.getFlags()); serializer.attributeInt(null, "privateFlags", pkg.getPrivateFlags()); serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime()); - serializer.attributeLongHex(null, "it", pkg.getFirstInstallTime()); serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime()); serializer.attributeLong(null, "version", pkg.getVersionCode()); if (pkg.getSharedUser() == null) { @@ -2918,6 +2934,11 @@ public final class Settings implements Watchable, Snappable { mKeySetRefs.clear(); mInstallerPackages.clear(); + // If any user state doesn't have a first install time, e.g., after an OTA, + // use the pre OTA firstInstallTime timestamp. This is because we migrated from per package + // firstInstallTime to per user-state. Without this, OTA can cause this info to be lost. + final ArrayMap<String, Long> originalFirstInstallTimes = new ArrayMap<>(); + try { if (str == null) { if (!mSettingsFilename.exists()) { @@ -2958,7 +2979,7 @@ public final class Settings implements Watchable, Snappable { String tagName = parser.getName(); if (tagName.equals("package")) { - readPackageLPw(parser, users); + readPackageLPw(parser, users, originalFirstInstallTimes); } else if (tagName.equals("permissions")) { mPermissions.readPermissions(parser); } else if (tagName.equals("permission-trees")) { @@ -3095,12 +3116,13 @@ public final class Settings implements Watchable, Snappable { writePackageRestrictionsLPr(UserHandle.USER_SYSTEM); } else { for (UserInfo user : users) { - readPackageRestrictionsLPr(user.id); + readPackageRestrictionsLPr(user.id, originalFirstInstallTimes); } } for (UserInfo user : users) { - mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id); + mRuntimePermissionsPersistence.readStateForUserSync(user.id, getInternalVersion(), + mPackages, mSharedUsers, getUserRuntimePermissionsFile(user.id)); } /* @@ -3124,7 +3146,8 @@ public final class Settings implements Watchable, Snappable { } void readPermissionStateForUserSyncLPr(@UserIdInt int userId) { - mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId); + mRuntimePermissionsPersistence.readStateForUserSync(userId, getInternalVersion(), + mPackages, mSharedUsers, getUserRuntimePermissionsFile(userId)); } void applyDefaultPreferredAppsLPw(int userId) { @@ -3523,7 +3546,6 @@ public final class Settings implements Watchable, Snappable { timeStamp = parser.getAttributeLong(null, "ts", 0); } ps.setLastModifiedTime(timeStamp); - ps.setFirstInstallTime(parser.getAttributeLongHex(null, "it", 0)); ps.setLastUpdateTime(parser.getAttributeLongHex(null, "ut", 0)); ps.setAppId(parser.getAttributeInt(null, "userId", 0)); if (ps.getAppId() <= 0) { @@ -3561,7 +3583,8 @@ public final class Settings implements Watchable, Snappable { private static int PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE = 1<<28; private static int PRE_M_APP_INFO_FLAG_PRIVILEGED = 1<<30; - private void readPackageLPw(TypedXmlPullParser parser, List<UserInfo> users) + private void readPackageLPw(TypedXmlPullParser parser, List<UserInfo> users, + ArrayMap<String, Long> originalFirstInstallTimes) throws XmlPullParserException, IOException { String name = null; String realName = null; @@ -3717,7 +3740,6 @@ public final class Settings implements Watchable, Snappable { + parser.getPositionDescription()); } else { packageSetting.setLastModifiedTime(timeStamp); - packageSetting.setFirstInstallTime(firstInstallTime); packageSetting.setLastUpdateTime(lastUpdateTime); } } else if (sharedUserId != 0) { @@ -3732,7 +3754,6 @@ public final class Settings implements Watchable, Snappable { null /* usesStaticLibraryVersions */, null /* mimeGroups */, domainSetId); packageSetting.setLastModifiedTime(timeStamp); - packageSetting.setFirstInstallTime(firstInstallTime); packageSetting.setLastUpdateTime(lastUpdateTime); mPendingPackages.add(packageSetting); if (PackageManagerService.DEBUG_SETTINGS) @@ -3867,6 +3888,9 @@ public final class Settings implements Watchable, Snappable { XmlUtils.skipCurrentTag(parser); } } + if (firstInstallTime != 0) { + originalFirstInstallTimes.put(packageSetting.getPackageName(), firstInstallTime); + } } else { XmlUtils.skipCurrentTag(parser); } @@ -4120,7 +4144,7 @@ public final class Settings implements Watchable, Snappable { file.delete(); removeCrossProfileIntentFiltersLPw(userId); - mRuntimePermissionsPersistence.onUserRemovedLPw(userId); + mRuntimePermissionsPersistence.onUserRemoved(userId); mDomainVerificationManager.clearUser(userId); writePackageListLPr(); @@ -4462,8 +4486,6 @@ public final class Settings implements Watchable, Snappable { pw.print(","); pw.print(ps.getVersionCode()); pw.print(","); - pw.print(ps.getFirstInstallTime()); - pw.print(","); pw.print(ps.getLastUpdateTime()); pw.print(","); pw.print(ps.getInstallSource().installerPackageName != null @@ -4485,27 +4507,30 @@ public final class Settings implements Watchable, Snappable { } } for (UserInfo user : users) { + final PackageUserStateInternal userState = ps.getUserStateOrDefault(user.id); pw.print(checkinTag); pw.print("-"); pw.print("usr"); pw.print(","); pw.print(user.id); pw.print(","); - pw.print(ps.getInstalled(user.id) ? "I" : "i"); - pw.print(ps.getHidden(user.id) ? "B" : "b"); - pw.print(ps.getSuspended(user.id) ? "SU" : "su"); - pw.print(ps.getStopped(user.id) ? "S" : "s"); - pw.print(ps.getNotLaunched(user.id) ? "l" : "L"); - pw.print(ps.getInstantApp(user.id) ? "IA" : "ia"); - pw.print(ps.getVirtualPreload(user.id) ? "VPI" : "vpi"); - String harmfulAppWarning = ps.getHarmfulAppWarning(user.id); + pw.print(userState.isInstalled() ? "I" : "i"); + pw.print(userState.isHidden() ? "B" : "b"); + pw.print(userState.isSuspended() ? "SU" : "su"); + pw.print(userState.isStopped() ? "S" : "s"); + pw.print(userState.isNotLaunched() ? "l" : "L"); + pw.print(userState.isInstantApp() ? "IA" : "ia"); + pw.print(userState.isVirtualPreload() ? "VPI" : "vpi"); + String harmfulAppWarning = userState.getHarmfulAppWarning(); pw.print(harmfulAppWarning != null ? "HA" : "ha"); pw.print(","); - pw.print(ps.getEnabled(user.id)); - String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id); + pw.print(userState.getEnabledState()); + String lastDisabledAppCaller = userState.getLastDisableAppCaller(); pw.print(","); pw.print(lastDisabledAppCaller != null ? lastDisabledAppCaller : "?"); pw.print(","); + pw.print(ps.readUserState(user.id).getFirstInstallTime()); + pw.print(","); pw.println(); } return; @@ -4736,9 +4761,6 @@ public final class Settings implements Watchable, Snappable { pw.print(prefix); pw.print(" timeStamp="); date.setTime(ps.getLastModifiedTime()); pw.println(sdf.format(date)); - pw.print(prefix); pw.print(" firstInstallTime="); - date.setTime(ps.getFirstInstallTime()); - pw.println(sdf.format(date)); pw.print(prefix); pw.print(" lastUpdateTime="); date.setTime(ps.getLastUpdateTime()); pw.println(sdf.format(date)); @@ -4819,48 +4841,53 @@ public final class Settings implements Watchable, Snappable { } for (UserInfo user : users) { + final PackageUserStateInternal userState = ps.getUserStateOrDefault(user.id); pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": "); pw.print("ceDataInode="); - pw.print(ps.getCeDataInode(user.id)); + pw.print(userState.getCeDataInode()); pw.print(" installed="); - pw.print(ps.getInstalled(user.id)); + pw.print(userState.isInstalled()); pw.print(" hidden="); - pw.print(ps.getHidden(user.id)); + pw.print(userState.isHidden()); pw.print(" suspended="); - pw.print(ps.getSuspended(user.id)); + pw.print(userState.isSuspended()); pw.print(" distractionFlags="); - pw.print(ps.getDistractionFlags(user.id)); + pw.print(userState.getDistractionFlags()); pw.print(" stopped="); - pw.print(ps.getStopped(user.id)); + pw.print(userState.isStopped()); pw.print(" notLaunched="); - pw.print(ps.getNotLaunched(user.id)); + pw.print(userState.isNotLaunched()); pw.print(" enabled="); - pw.print(ps.getEnabled(user.id)); + pw.print(userState.getEnabledState()); pw.print(" instant="); - pw.print(ps.getInstantApp(user.id)); + pw.print(userState.isInstantApp()); pw.print(" virtual="); - pw.print(ps.getVirtualPreload(user.id)); + pw.println(userState.isVirtualPreload()); pw.print(" installReason="); - pw.println(ps.getInstallReason(user.id)); + pw.println(userState.getInstallReason()); + + final PackageUserStateInternal pus = ps.readUserState(user.id); + pw.print(" firstInstallTime="); + date.setTime(pus.getFirstInstallTime()); + pw.println(sdf.format(date)); - if (ps.getSuspended(user.id)) { + if (userState.isSuspended()) { pw.print(prefix); pw.println(" Suspend params:"); - final PackageUserStateInternal pus = ps.readUserState(user.id); - for (int i = 0; i < pus.getSuspendParams().size(); i++) { + for (int i = 0; i < userState.getSuspendParams().size(); i++) { pw.print(prefix); pw.print(" suspendingPackage="); - pw.print(pus.getSuspendParams().keyAt(i)); - final SuspendParams params = pus.getSuspendParams().valueAt(i); + pw.print(userState.getSuspendParams().keyAt(i)); + final SuspendParams params = userState.getSuspendParams().valueAt(i); if (params != null) { pw.print(" dialogInfo="); - pw.print(params.dialogInfo); + pw.print(params.getDialogInfo()); } pw.println(); } } - final OverlayPaths overlayPaths = ps.getOverlayPaths(user.id); + final OverlayPaths overlayPaths = userState.getOverlayPaths(); if (overlayPaths != null) { if (!overlayPaths.getOverlayPaths().isEmpty()) { pw.print(prefix); @@ -4883,7 +4910,7 @@ public final class Settings implements Watchable, Snappable { } final Map<String, OverlayPaths> sharedLibraryOverlayPaths = - ps.getOverlayPathsForLibrary(user.id); + userState.getSharedLibraryOverlayPaths(); if (sharedLibraryOverlayPaths != null) { for (Map.Entry<String, OverlayPaths> libOverlayPaths : sharedLibraryOverlayPaths.entrySet()) { @@ -4916,7 +4943,7 @@ public final class Settings implements Watchable, Snappable { } } - String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id); + String lastDisabledAppCaller = userState.getLastDisableAppCaller(); if (lastDisabledAppCaller != null) { pw.print(prefix); pw.print(" lastDisabledCaller: "); pw.println(lastDisabledAppCaller); @@ -4929,21 +4956,21 @@ public final class Settings implements Watchable, Snappable { .getPermissionStates(user.id), dumpAll); } - String harmfulAppWarning = ps.getHarmfulAppWarning(user.id); + String harmfulAppWarning = userState.getHarmfulAppWarning(); if (harmfulAppWarning != null) { pw.print(prefix); pw.print(" harmfulAppWarning: "); pw.println(harmfulAppWarning); } if (permissionNames == null) { - ArraySet<String> cmp = ps.getDisabledComponents(user.id); + Set<String> cmp = userState.getDisabledComponents(); if (cmp != null && cmp.size() > 0) { pw.print(prefix); pw.println(" disabledComponents:"); for (String s : cmp) { pw.print(prefix); pw.print(" "); pw.println(s); } } - cmp = ps.getEnabledComponents(user.id); + cmp = userState.getEnabledComponents(); if (cmp != null && cmp.size() > 0) { pw.print(prefix); pw.println(" enabledComponents:"); for (String s : cmp) { @@ -5291,9 +5318,10 @@ public final class Settings implements Watchable, Snappable { public void writePermissionStateForUserLPr(int userId, boolean sync) { if (sync) { - mRuntimePermissionsPersistence.writeStateForUserSyncLPr(userId); + mRuntimePermissionsPersistence.writeStateForUserSync(userId, mPermissionDataProvider, + mPackages, mSharedUsers); } else { - mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId); + mRuntimePermissionsPersistence.writeStateForUserAsync(userId); } } @@ -5368,7 +5396,7 @@ public final class Settings implements Watchable, Snappable { } } - private final class RuntimePermissionPersistence { + private static final class RuntimePermissionPersistence { private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200; private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000; @@ -5381,6 +5409,8 @@ public final class Settings implements Watchable, Snappable { private final Handler mHandler = new MyHandler(); + private final Object mLock = new Object(); + @GuardedBy("mLock") private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray(); @@ -5400,45 +5430,58 @@ public final class Settings implements Watchable, Snappable { // The mapping keys are user ids. private final SparseBooleanArray mPermissionUpgradeNeeded = new SparseBooleanArray(); - public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence) { + // This is a hack to allow this class to invoke a write using Settings's data structures, + // to facilitate moving to a finer scoped lock without a significant refactor. + private final Consumer<Integer> mInvokeWriteUserStateAsyncCallback; + + public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence, + Consumer<Integer> invokeWriteUserStateAsyncCallback) { mPersistence = persistence; + mInvokeWriteUserStateAsyncCallback = invokeWriteUserStateAsyncCallback; } - @GuardedBy("Settings.this.mLock") - int getVersionLPr(int userId) { - return mVersions.get(userId, INITIAL_VERSION); + int getVersion(int userId) { + synchronized (mLock) { + return mVersions.get(userId, INITIAL_VERSION); + } } - @GuardedBy("Settings.this.mLock") - void setVersionLPr(int version, int userId) { - mVersions.put(userId, version); - writeStateForUserAsyncLPr(userId); + void setVersion(int version, int userId) { + synchronized (mLock) { + mVersions.put(userId, version); + writeStateForUserAsync(userId); + } } - @GuardedBy("Settings.this.mLock") public boolean isPermissionUpgradeNeeded(int userId) { - return mPermissionUpgradeNeeded.get(userId, true); + synchronized (mLock) { + return mPermissionUpgradeNeeded.get(userId, true); + } } - @GuardedBy("Settings.this.mLock") - public void updateRuntimePermissionsFingerprintLPr(@UserIdInt int userId) { - if (mExtendedFingerprint == null) { - throw new RuntimeException("The version of the permission controller hasn't been " - + "set before trying to update the fingerprint."); + public void updateRuntimePermissionsFingerprint(@UserIdInt int userId) { + synchronized (mLock) { + if (mExtendedFingerprint == null) { + throw new RuntimeException( + "The version of the permission controller hasn't been " + + "set before trying to update the fingerprint."); + } + mFingerprints.put(userId, mExtendedFingerprint); + writeStateForUserAsync(userId); } - mFingerprints.put(userId, mExtendedFingerprint); - writeStateForUserAsyncLPr(userId); } public void setPermissionControllerVersion(long version) { - int numUser = mFingerprints.size(); - mExtendedFingerprint = getExtendedFingerprint(version); + synchronized (mLock) { + int numUser = mFingerprints.size(); + mExtendedFingerprint = getExtendedFingerprint(version); - for (int i = 0; i < numUser; i++) { - int userId = mFingerprints.keyAt(i); - String fingerprint = mFingerprints.valueAt(i); - mPermissionUpgradeNeeded.put(userId, - !TextUtils.equals(mExtendedFingerprint, fingerprint)); + for (int i = 0; i < numUser; i++) { + int userId = mFingerprints.keyAt(i); + String fingerprint = mFingerprints.valueAt(i); + mPermissionUpgradeNeeded.put(userId, + !TextUtils.equals(mExtendedFingerprint, fingerprint)); + } } } @@ -5446,84 +5489,92 @@ public final class Settings implements Watchable, Snappable { return PackagePartitions.FINGERPRINT + "?pc_version=" + version; } - public void writeStateForUserAsyncLPr(int userId) { - final long currentTimeMillis = SystemClock.uptimeMillis(); + public void writeStateForUserAsync(int userId) { + synchronized (mLock) { + final long currentTimeMillis = SystemClock.uptimeMillis(); - if (mWriteScheduled.get(userId)) { - mHandler.removeMessages(userId); + if (mWriteScheduled.get(userId)) { + mHandler.removeMessages(userId); - // If enough time passed, write without holding off anymore. - final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis - .get(userId); - final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis - - lastNotWrittenMutationTimeMillis; - if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) { - mHandler.obtainMessage(userId).sendToTarget(); - return; - } + // If enough time passed, write without holding off anymore. + final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis + .get(userId); + final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis + - lastNotWrittenMutationTimeMillis; + if (timeSinceLastNotWrittenMutationMillis + >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) { + mHandler.obtainMessage(userId).sendToTarget(); + return; + } - // Hold off a bit more as settings are frequently changing. - final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis - + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0); - final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS, - maxDelayMillis); + // Hold off a bit more as settings are frequently changing. + final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis + + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0); + final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS, + maxDelayMillis); - Message message = mHandler.obtainMessage(userId); - mHandler.sendMessageDelayed(message, writeDelayMillis); - } else { - mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis); - Message message = mHandler.obtainMessage(userId); - mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS); - mWriteScheduled.put(userId, true); + Message message = mHandler.obtainMessage(userId); + mHandler.sendMessageDelayed(message, writeDelayMillis); + } else { + mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis); + Message message = mHandler.obtainMessage(userId); + mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS); + mWriteScheduled.put(userId, true); + } } } - public void writeStateForUserSyncLPr(int userId) { - mHandler.removeMessages(userId); - mWriteScheduled.delete(userId); - - mPermissionDataProvider.writeLegacyPermissionStateTEMP(); - - int version = mVersions.get(userId, INITIAL_VERSION); - - String fingerprint = mFingerprints.get(userId); + public void writeStateForUserSync(int userId, @NonNull LegacyPermissionDataProvider + legacyPermissionDataProvider, + @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates, + @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) { + synchronized (mLock) { + mHandler.removeMessages(userId); + mWriteScheduled.delete(userId); + + legacyPermissionDataProvider.writeLegacyPermissionStateTEMP(); + + int version = mVersions.get(userId, INITIAL_VERSION); + + String fingerprint = mFingerprints.get(userId); + + Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = + new ArrayMap<>(); + int packagesSize = packageStates.size(); + for (int i = 0; i < packagesSize; i++) { + String packageName = packageStates.keyAt(i); + PackageStateInternal packageState = packageStates.valueAt(i); + if (packageState.getSharedUser() == null) { + List<RuntimePermissionsState.PermissionState> permissions = + getPermissionsFromPermissionsState( + packageState.getLegacyPermissionState(), userId); + if (permissions.isEmpty() && !packageState.isInstallPermissionsFixed()) { + // Storing an empty state means the package is known to the system and + // its install permissions have been granted and fixed. If this is not + // the case, we should not store anything. + continue; + } + packagePermissions.put(packageName, permissions); + } + } - Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = - new ArrayMap<>(); - int packagesSize = mPackages.size(); - for (int i = 0; i < packagesSize; i++) { - String packageName = mPackages.keyAt(i); - PackageSetting packageSetting = mPackages.valueAt(i); - if (packageSetting.getSharedUser() == null) { + Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = + new ArrayMap<>(); + final int sharedUsersSize = sharedUsers.size(); + for (int i = 0; i < sharedUsersSize; i++) { + String sharedUserName = sharedUsers.keyAt(i); + SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i); List<RuntimePermissionsState.PermissionState> permissions = getPermissionsFromPermissionsState( - packageSetting.getLegacyPermissionState(), userId); - if (permissions.isEmpty() && !packageSetting.isInstallPermissionsFixed()) { - // Storing an empty state means the package is known to the system and its - // install permissions have been granted and fixed. If this is not the case, - // we should not store anything. - continue; - } - packagePermissions.put(packageName, permissions); + sharedUserSetting.getLegacyPermissionState(), userId); + sharedUserPermissions.put(sharedUserName, permissions); } - } - - Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = - new ArrayMap<>(); - final int sharedUsersSize = mSharedUsers.size(); - for (int i = 0; i < sharedUsersSize; i++) { - String sharedUserName = mSharedUsers.keyAt(i); - SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i); - List<RuntimePermissionsState.PermissionState> permissions = - getPermissionsFromPermissionsState( - sharedUserSetting.getLegacyPermissionState(), userId); - sharedUserPermissions.put(sharedUserName, permissions); - } - RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version, - fingerprint, packagePermissions, sharedUserPermissions); + RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version, + fingerprint, packagePermissions, sharedUserPermissions); - mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId)); + mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId)); + } } @NonNull @@ -5541,82 +5592,91 @@ public final class Settings implements Watchable, Snappable { return permissions; } - @GuardedBy("Settings.this.mLock") - private void onUserRemovedLPw(int userId) { - // Make sure we do not - mHandler.removeMessages(userId); + private void onUserRemoved(int userId) { + synchronized (mLock) { + // Make sure we do not + mHandler.removeMessages(userId); - mPermissionUpgradeNeeded.delete(userId); - mVersions.delete(userId); - mFingerprints.remove(userId); + mPermissionUpgradeNeeded.delete(userId); + mVersions.delete(userId); + mFingerprints.remove(userId); + } } public void deleteUserRuntimePermissionsFile(int userId) { - mPersistence.deleteForUser(UserHandle.of(userId)); - } - - @GuardedBy("Settings.this.mLock") - public void readStateForUserSyncLPr(int userId) { - RuntimePermissionsState runtimePermissions = mPersistence.readForUser(UserHandle.of( - userId)); - if (runtimePermissions == null) { - readLegacyStateForUserSyncLPr(userId); - writeStateForUserAsyncLPr(userId); - return; - } + synchronized (mLock) { + mPersistence.deleteForUser(UserHandle.of(userId)); + } + } + + public void readStateForUserSync(int userId, @NonNull VersionInfo internalVersion, + @NonNull WatchedArrayMap<String, PackageSetting> packageSettings, + @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers, + @NonNull File userRuntimePermissionsFile) { + synchronized (mLock) { + RuntimePermissionsState runtimePermissions = mPersistence.readForUser(UserHandle.of( + userId)); + if (runtimePermissions == null) { + readLegacyStateForUserSync(userId, userRuntimePermissionsFile, packageSettings, + sharedUsers); + writeStateForUserAsync(userId); + return; + } - // If the runtime permissions file exists but the version is not set this is - // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION. - int version = runtimePermissions.getVersion(); - if (version == RuntimePermissionsState.NO_VERSION) { - version = UPGRADE_VERSION; - } - mVersions.put(userId, version); + // If the runtime permissions file exists but the version is not set this is + // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION. + int version = runtimePermissions.getVersion(); + if (version == RuntimePermissionsState.NO_VERSION) { + version = UPGRADE_VERSION; + } + mVersions.put(userId, version); - String fingerprint = runtimePermissions.getFingerprint(); - mFingerprints.put(userId, fingerprint); + String fingerprint = runtimePermissions.getFingerprint(); + mFingerprints.put(userId, fingerprint); - boolean isUpgradeToR = getInternalVersion().sdkVersion < Build.VERSION_CODES.R; + boolean isUpgradeToR = internalVersion.sdkVersion < Build.VERSION_CODES.R; - Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = - runtimePermissions.getPackagePermissions(); - int packagesSize = mPackages.size(); - for (int i = 0; i < packagesSize; i++) { - String packageName = mPackages.keyAt(i); - PackageSetting packageSetting = mPackages.valueAt(i); + Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions = + runtimePermissions.getPackagePermissions(); + int packagesSize = packageSettings.size(); + for (int i = 0; i < packagesSize; i++) { + String packageName = packageSettings.keyAt(i); + PackageSetting packageSetting = packageSettings.valueAt(i); - List<RuntimePermissionsState.PermissionState> permissions = - packagePermissions.get(packageName); - if (permissions != null) { - readPermissionsStateLpr(permissions, packageSetting.getLegacyPermissionState(), - userId); - packageSetting.setInstallPermissionsFixed(true); - } else if (packageSetting.getSharedUser() == null && !isUpgradeToR) { - Slog.w(TAG, "Missing permission state for package: " + packageName); - packageSetting.getLegacyPermissionState().setMissing(true, userId); + List<RuntimePermissionsState.PermissionState> permissions = + packagePermissions.get(packageName); + if (permissions != null) { + readPermissionsState(permissions, + packageSetting.getLegacyPermissionState(), + userId); + packageSetting.setInstallPermissionsFixed(true); + } else if (packageSetting.getSharedUser() == null && !isUpgradeToR) { + Slog.w(TAG, "Missing permission state for package: " + packageName); + packageSetting.getLegacyPermissionState().setMissing(true, userId); + } } - } - Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = - runtimePermissions.getSharedUserPermissions(); - int sharedUsersSize = mSharedUsers.size(); - for (int i = 0; i < sharedUsersSize; i++) { - String sharedUserName = mSharedUsers.keyAt(i); - SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i); + Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions = + runtimePermissions.getSharedUserPermissions(); + int sharedUsersSize = sharedUsers.size(); + for (int i = 0; i < sharedUsersSize; i++) { + String sharedUserName = sharedUsers.keyAt(i); + SharedUserSetting sharedUserSetting = sharedUsers.valueAt(i); - List<RuntimePermissionsState.PermissionState> permissions = - sharedUserPermissions.get(sharedUserName); - if (permissions != null) { - readPermissionsStateLpr(permissions, - sharedUserSetting.getLegacyPermissionState(), userId); - } else if (!isUpgradeToR) { - Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName); - sharedUserSetting.getLegacyPermissionState().setMissing(true, userId); + List<RuntimePermissionsState.PermissionState> permissions = + sharedUserPermissions.get(sharedUserName); + if (permissions != null) { + readPermissionsState(permissions, + sharedUserSetting.getLegacyPermissionState(), userId); + } else if (!isUpgradeToR) { + Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName); + sharedUserSetting.getLegacyPermissionState().setMissing(true, userId); + } } } } - private void readPermissionsStateLpr( + private void readPermissionsState( @NonNull List<RuntimePermissionsState.PermissionState> permissions, @NonNull LegacyPermissionState permissionsState, @UserIdInt int userId) { int permissionsSize = permissions.size(); @@ -5630,77 +5690,86 @@ public final class Settings implements Watchable, Snappable { } } - @GuardedBy("Settings.this.mLock") - private void readLegacyStateForUserSyncLPr(int userId) { - File permissionsFile = getUserRuntimePermissionsFile(userId); - if (!permissionsFile.exists()) { - return; - } + private void readLegacyStateForUserSync(int userId, @NonNull File permissionsFile, + @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates, + @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) { + synchronized (mLock) { + if (!permissionsFile.exists()) { + return; + } - FileInputStream in; - try { - in = new AtomicFile(permissionsFile).openRead(); - } catch (FileNotFoundException fnfe) { - Slog.i(PackageManagerService.TAG, "No permissions state"); - return; - } + FileInputStream in; + try { + in = new AtomicFile(permissionsFile).openRead(); + } catch (FileNotFoundException fnfe) { + Slog.i(PackageManagerService.TAG, "No permissions state"); + return; + } - try { - final TypedXmlPullParser parser = Xml.resolvePullParser(in); - parseLegacyRuntimePermissionsLPr(parser, userId); + try { + final TypedXmlPullParser parser = Xml.resolvePullParser(in); + parseLegacyRuntimePermissions(parser, userId, packageStates, sharedUsers); - } catch (XmlPullParserException | IOException e) { - throw new IllegalStateException("Failed parsing permissions file: " - + permissionsFile, e); - } finally { - IoUtils.closeQuietly(in); + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed parsing permissions file: " + + permissionsFile, e); + } finally { + IoUtils.closeQuietly(in); + } } } - // Private internals - - @GuardedBy("Settings.this.mLock") - private void parseLegacyRuntimePermissionsLPr(TypedXmlPullParser parser, int userId) + private void parseLegacyRuntimePermissions(TypedXmlPullParser parser, int userId, + @NonNull WatchedArrayMap<String, ? extends PackageStateInternal> packageStates, + @NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers) throws IOException, XmlPullParserException { - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } + synchronized (mLock) { + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } - switch (parser.getName()) { - case TAG_RUNTIME_PERMISSIONS: { - // If the permisions settings file exists but the version is not set this is - // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION - int version = parser.getAttributeInt(null, ATTR_VERSION, UPGRADE_VERSION); - mVersions.put(userId, version); - String fingerprint = parser.getAttributeValue(null, ATTR_FINGERPRINT); - mFingerprints.put(userId, fingerprint); - } break; - - case TAG_PACKAGE: { - String name = parser.getAttributeValue(null, ATTR_NAME); - PackageSetting ps = mPackages.get(name); - if (ps == null) { - Slog.w(PackageManagerService.TAG, "Unknown package:" + name); - XmlUtils.skipCurrentTag(parser); - continue; + switch (parser.getName()) { + case TAG_RUNTIME_PERMISSIONS: { + // If the permisions settings file exists but the version is not set this is + // an upgrade from P->Q. Hence mark it with the special UPGRADE_VERSION + int version = parser.getAttributeInt(null, ATTR_VERSION, + UPGRADE_VERSION); + mVersions.put(userId, version); + String fingerprint = parser.getAttributeValue(null, ATTR_FINGERPRINT); + mFingerprints.put(userId, fingerprint); } - parseLegacyPermissionsLPr(parser, ps.getLegacyPermissionState(), userId); - } break; - - case TAG_SHARED_USER: { - String name = parser.getAttributeValue(null, ATTR_NAME); - SharedUserSetting sus = mSharedUsers.get(name); - if (sus == null) { - Slog.w(PackageManagerService.TAG, "Unknown shared user:" + name); - XmlUtils.skipCurrentTag(parser); - continue; + break; + + case TAG_PACKAGE: { + String name = parser.getAttributeValue(null, ATTR_NAME); + PackageStateInternal ps = packageStates.get(name); + if (ps == null) { + Slog.w(PackageManagerService.TAG, "Unknown package:" + name); + XmlUtils.skipCurrentTag(parser); + continue; + } + parseLegacyPermissionsLPr(parser, ps.getLegacyPermissionState(), + userId); + } + break; + + case TAG_SHARED_USER: { + String name = parser.getAttributeValue(null, ATTR_NAME); + SharedUserSetting sus = sharedUsers.get(name); + if (sus == null) { + Slog.w(PackageManagerService.TAG, "Unknown shared user:" + name); + XmlUtils.skipCurrentTag(parser); + continue; + } + parseLegacyPermissionsLPr(parser, sus.getLegacyPermissionState(), + userId); } - parseLegacyPermissionsLPr(parser, sus.getLegacyPermissionState(), userId); - } break; + break; + } } } } @@ -5708,25 +5777,27 @@ public final class Settings implements Watchable, Snappable { private void parseLegacyPermissionsLPr(TypedXmlPullParser parser, LegacyPermissionState permissionsState, int userId) throws IOException, XmlPullParserException { - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } + synchronized (mLock) { + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } - switch (parser.getName()) { - case TAG_ITEM: { - String name = parser.getAttributeValue(null, ATTR_NAME); - final boolean granted = - parser.getAttributeBoolean(null, ATTR_GRANTED, true); - final int flags = - parser.getAttributeIntHex(null, ATTR_FLAGS, 0); - permissionsState.putPermissionState(new PermissionState(name, true, - granted, flags), userId); + switch (parser.getName()) { + case TAG_ITEM: { + String name = parser.getAttributeValue(null, ATTR_NAME); + final boolean granted = + parser.getAttributeBoolean(null, ATTR_GRANTED, true); + final int flags = + parser.getAttributeIntHex(null, ATTR_FLAGS, 0); + permissionsState.putPermissionState(new PermissionState(name, true, + granted, flags), userId); + } + break; } - break; } } } @@ -5740,9 +5811,7 @@ public final class Settings implements Watchable, Snappable { public void handleMessage(Message message) { final int userId = message.what; Runnable callback = (Runnable) message.obj; - synchronized (mLock) { - writeStateForUserSyncLPr(userId); - } + mInvokeWriteUserStateAsyncCallback.accept(userId); if (callback != null) { callback.run(); } diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java new file mode 100644 index 000000000000..0055f4eb6446 --- /dev/null +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -0,0 +1,824 @@ +/* + * 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.server.pm; + +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; +import static com.android.server.pm.PackageManagerService.TAG; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManager; +import android.content.pm.SharedLibraryInfo; +import android.content.pm.VersionedPackage; +import android.os.Process; +import android.os.UserHandle; +import android.os.storage.StorageManager; +import android.service.pm.PackageServiceDumpProto; +import android.util.ArraySet; +import android.util.Pair; +import android.util.Slog; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; +import com.android.server.SystemConfig; +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.utils.Snappable; +import com.android.server.utils.SnapshotCache; +import com.android.server.utils.Watchable; +import com.android.server.utils.WatchableImpl; +import com.android.server.utils.Watched; +import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedLongSparseArray; +import com.android.server.utils.Watcher; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +/** + * Current known shared libraries on the device. + */ +public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable { + + // TODO(b/200588896): remove PMS dependency + private final PackageManagerService mPm; + private final PackageManagerServiceInjector mInjector; + private DeletePackageHelper mDeletePackageHelper; // late init + + // A map of library name to a list of {@link SharedLibraryInfo}s with different versions. + @Watched + private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> + mSharedLibraries; + private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>> + mSharedLibrariesSnapshot; + + // A map of declaring package name to a list of {@link SharedLibraryInfo}s with different + // versions. + @Watched + private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> + mStaticLibsByDeclaringPackage; + private final SnapshotCache<WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>>> + mStaticLibsByDeclaringPackageSnapshot; + + /** + * Watchable machinery + */ + private final WatchableImpl mWatchable = new WatchableImpl(); + + /** + * The observer that watches for changes from array members + */ + private final Watcher mObserver = new Watcher() { + @Override + public void onChange(@Nullable Watchable what) { + SharedLibrariesImpl.this.dispatchChange(what); + } + }; + + private final SnapshotCache<SharedLibrariesImpl> mSnapshot; + + // Create a snapshot cache + private SnapshotCache<SharedLibrariesImpl> makeCache() { + return new SnapshotCache<SharedLibrariesImpl>(this /* source */, this /* watchable */) { + @Override + public SharedLibrariesImpl createSnapshot() { + final SharedLibrariesImpl sharedLibrariesImpl = new SharedLibrariesImpl(mSource); + sharedLibrariesImpl.mWatchable.seal(); + return sharedLibrariesImpl; + }}; + } + + /** + * Default constructor used in PackageManagerService. + */ + SharedLibrariesImpl(PackageManagerService pm, PackageManagerServiceInjector injector) { + mPm = pm; + mInjector = injector; + + mSharedLibraries = new WatchedArrayMap<>(); + mSharedLibrariesSnapshot = new SnapshotCache.Auto<>(mSharedLibraries, mSharedLibraries, + "SharedLibrariesImpl.mSharedLibraries"); + mStaticLibsByDeclaringPackage = new WatchedArrayMap<>(); + mStaticLibsByDeclaringPackageSnapshot = new SnapshotCache.Auto<>( + mStaticLibsByDeclaringPackage, mStaticLibsByDeclaringPackage, + "SharedLibrariesImpl.mStaticLibsByDeclaringPackage"); + + registerObservers(); + Watchable.verifyWatchedAttributes(this, mObserver); + mSnapshot = makeCache(); + } + + /** + * Invoked by PMS constructor after the instance of {@link DeletePackageHelper} is ready. + */ + void setDeletePackageHelper(DeletePackageHelper deletePackageHelper) { + mDeletePackageHelper = deletePackageHelper; + } + + private void registerObservers() { + mSharedLibraries.registerObserver(mObserver); + mStaticLibsByDeclaringPackage.registerObserver(mObserver); + } + + /** + * A copy constructor used in snapshot(). + */ + private SharedLibrariesImpl(SharedLibrariesImpl source) { + mPm = source.mPm; + mInjector = source.mInjector; + + mSharedLibraries = source.mSharedLibrariesSnapshot.snapshot(); + mSharedLibrariesSnapshot = new SnapshotCache.Sealed<>(); + mStaticLibsByDeclaringPackage = source.mStaticLibsByDeclaringPackageSnapshot.snapshot(); + mStaticLibsByDeclaringPackageSnapshot = new SnapshotCache.Sealed<>(); + + // Do not register any Watchables and do not create a snapshot cache. + mSnapshot = new SnapshotCache.Sealed(); + } + + /** + * Ensures an observer is in the list, exactly once. The observer cannot be null. The + * function quietly returns if the observer is already in the list. + * + * @param observer The {@link Watcher} to be notified when the {@link Watchable} changes. + */ + @Override + public void registerObserver(@NonNull Watcher observer) { + mWatchable.registerObserver(observer); + } + + /** + * Ensures an observer is not in the list. The observer must not be null. The function + * quietly returns if the objserver is not in the list. + * + * @param observer The {@link Watcher} that should not be in the notification list. + */ + @Override + public void unregisterObserver(@NonNull Watcher observer) { + mWatchable.unregisterObserver(observer); + } + + /** + * Return true if the {@link Watcher} is a registered observer. + * @param observer A {@link Watcher} that might be registered + * @return true if the observer is registered with this {@link Watchable}. + */ + @Override + public boolean isRegisteredObserver(@NonNull Watcher observer) { + return mWatchable.isRegisteredObserver(observer); + } + + /** + * Invokes {@link Watcher#onChange} on each registered observer. The method can be called + * with the {@link Watchable} that generated the event. In a tree of {@link Watchable}s, this + * is generally the first (deepest) {@link Watchable} to detect a change. + * + * @param what The {@link Watchable} that generated the event. + */ + @Override + public void dispatchChange(@Nullable Watchable what) { + mWatchable.dispatchChange(what); + } + + /** + * Create an immutable copy of the object, suitable for read-only methods. A snapshot + * is free to omit state that is only needed for mutating methods. + */ + @Override + public @NonNull SharedLibrariesRead snapshot() { + return mSnapshot.snapshot(); + } + + /** + * Returns all shared libraries on the device. + */ + @GuardedBy("mPm.mLock") + @Override + public @NonNull WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getAll() { + return mSharedLibraries; + } + + /** + * Given the library name, returns a list of shared libraries on all versions. + */ + @GuardedBy("mPm.mLock") + @Override + public @NonNull WatchedLongSparseArray<SharedLibraryInfo> getSharedLibraryInfos( + @NonNull String libName) { + return mSharedLibraries.get(libName); + } + + /** + * Returns the shared library with given library name and version number. + */ + @GuardedBy("mPm.mLock") + @Override + public @Nullable SharedLibraryInfo getSharedLibraryInfo(@NonNull String libName, long version) { + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + mSharedLibraries.get(libName); + if (versionedLib == null) { + return null; + } + return versionedLib.get(version); + } + + /** + * Given the declaring package name, returns a list of static shared libraries on all versions. + */ + @GuardedBy("mPm.mLock") + @Override + public @NonNull WatchedLongSparseArray<SharedLibraryInfo> getStaticLibraryInfos( + @NonNull String declaringPackageName) { + return mStaticLibsByDeclaringPackage.get(declaringPackageName); + } + + @GuardedBy("mPm.mLock") + private @Nullable PackageSetting getLibraryPackageLPr(@NonNull SharedLibraryInfo libInfo) { + final VersionedPackage declaringPackage = libInfo.getDeclaringPackage(); + if (libInfo.isStatic()) { + // Resolve the package name - we use synthetic package names internally + final String internalPackageName = mPm.resolveInternalPackageNameLPr( + declaringPackage.getPackageName(), + declaringPackage.getLongVersionCode()); + return mPm.mSettings.getPackageLPr(internalPackageName); + } + if (libInfo.isSdk()) { + return mPm.mSettings.getPackageLPr(declaringPackage.getPackageName()); + } + return null; + } + + /** + * Finds all unused shared libraries which have cached more than the given + * {@code maxCachePeriod}. Deletes them one by one until the available storage space on the + * device is larger than {@code neededSpace}. + * + * @param neededSpace A minimum available storage space the device needs to reach + * @param maxCachePeriod A maximum period of time an unused shared library can be cached + * on the device. + * @return {@code true} if the available storage space is reached. + */ + boolean pruneUnusedStaticSharedLibraries(long neededSpace, long maxCachePeriod) + throws IOException { + final StorageManager storage = mInjector.getSystemService(StorageManager.class); + final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); + + List<VersionedPackage> packagesToDelete = null; + final long now = System.currentTimeMillis(); + + // Important: We skip shared libs used for some user since + // in such a case we need to keep the APK on the device. The check for + // a lib being used for any user is performed by the uninstall call. + synchronized (mPm.mLock) { + final int libCount = mSharedLibraries.size(); + for (int i = 0; i < libCount; i++) { + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + mSharedLibraries.valueAt(i); + if (versionedLib == null) { + continue; + } + final int versionCount = versionedLib.size(); + for (int j = 0; j < versionCount; j++) { + SharedLibraryInfo libInfo = versionedLib.valueAt(j); + final PackageSetting ps = getLibraryPackageLPr(libInfo); + if (ps == null) { + continue; + } + // Skip unused libs cached less than the min period to prevent pruning a lib + // needed by a subsequently installed package. + if (now - ps.getLastUpdateTime() < maxCachePeriod) { + continue; + } + + if (ps.getPkg().isSystem()) { + continue; + } + + if (packagesToDelete == null) { + packagesToDelete = new ArrayList<>(); + } + packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(), + libInfo.getDeclaringPackage().getLongVersionCode())); + } + } + } + + if (packagesToDelete != null) { + final int packageCount = packagesToDelete.size(); + for (int i = 0; i < packageCount; i++) { + final VersionedPackage pkgToDelete = packagesToDelete.get(i); + // Delete the package synchronously (will fail of the lib used for any user). + if (mDeletePackageHelper.deletePackageX(pkgToDelete.getPackageName(), + pkgToDelete.getLongVersionCode(), UserHandle.USER_SYSTEM, + PackageManager.DELETE_ALL_USERS, + true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) { + if (volume.getUsableSpace() >= neededSpace) { + return true; + } + } + } + } + + return false; + } + + /** + * Given a package of static shared library, returns its shared library info of + * the latest version. + * + * @param pkg A package of static shared library. + * @return The latest version of shared library info. + */ + @GuardedBy("mPm.mLock") + @Nullable SharedLibraryInfo getLatestSharedLibraVersionLPr(@NonNull AndroidPackage pkg) { + WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get( + pkg.getStaticSharedLibName()); + if (versionedLib == null) { + return null; + } + long previousLibVersion = -1; + final int versionCount = versionedLib.size(); + for (int i = 0; i < versionCount; i++) { + final long libVersion = versionedLib.keyAt(i); + if (libVersion < pkg.getStaticSharedLibVersion()) { + previousLibVersion = Math.max(previousLibVersion, libVersion); + } + } + if (previousLibVersion >= 0) { + return versionedLib.get(previousLibVersion); + } + return null; + } + + /** + * Given a package scanned result of a static shared library, returns its package setting of + * the latest version + * + * @param scanResult The scanned result of a static shared library package. + * @return The package setting that represents the latest version of shared library info. + */ + @Nullable + PackageSetting getStaticSharedLibLatestVersionSetting(@NonNull ScanResult scanResult) { + PackageSetting sharedLibPackage = null; + synchronized (mPm.mLock) { + final SharedLibraryInfo latestSharedLibraVersionLPr = + getLatestSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage); + if (latestSharedLibraVersionLPr != null) { + sharedLibPackage = mPm.mSettings.getPackageLPr( + latestSharedLibraVersionLPr.getPackageName()); + } + } + return sharedLibPackage; + } + + /** + * Apply a given {@code action} to all the libraries defining in the package. + * + * @param pkg A package defining libraries. + * @param libInfo An extra shared library info passing to the action. + * @param action The action to apply. + */ + @GuardedBy("mPm.mLock") + private void applyDefiningSharedLibraryUpdateLPr( + @NonNull AndroidPackage pkg, @Nullable SharedLibraryInfo libInfo, + @NonNull BiConsumer<SharedLibraryInfo, SharedLibraryInfo> action) { + // Note that libraries defined by this package may be null if: + // - Package manager was unable to create the shared library. The package still + // gets installed, but the shared library does not get created. + // Or: + // - Package manager is in a state where package isn't scanned yet. This will + // get called again after scanning to fix the dependencies. + if (AndroidPackageUtils.isLibrary(pkg)) { + if (pkg.getSdkLibName() != null) { + SharedLibraryInfo definedLibrary = getSharedLibraryInfo( + pkg.getSdkLibName(), pkg.getSdkLibVersionMajor()); + if (definedLibrary != null) { + action.accept(definedLibrary, libInfo); + } + } else if (pkg.getStaticSharedLibName() != null) { + SharedLibraryInfo definedLibrary = getSharedLibraryInfo( + pkg.getStaticSharedLibName(), pkg.getStaticSharedLibVersion()); + if (definedLibrary != null) { + action.accept(definedLibrary, libInfo); + } + } else { + for (String libraryName : pkg.getLibraryNames()) { + SharedLibraryInfo definedLibrary = getSharedLibraryInfo( + libraryName, SharedLibraryInfo.VERSION_UNDEFINED); + if (definedLibrary != null) { + action.accept(definedLibrary, libInfo); + } + } + } + } + } + + /** + * Adds shared library {@code libInfo}'s self code paths and using library files to the list + * {@code usesLibraryFiles}. Also, adds the dependencies to the shared libraries that are + * defining in the {@code pkg}. + * + * @param pkg A package that is using the {@code libInfo}. + * @param usesLibraryFiles A list to add code paths to. + * @param libInfo A shared library info that is used by the {@code pkg}. + * @param changingLib The updating library package. + * @param changingLibSetting The updating library package setting. + */ + @GuardedBy("mPm.mLock") + private void addSharedLibraryLPr(@NonNull AndroidPackage pkg, + @NonNull Set<String> usesLibraryFiles, @NonNull SharedLibraryInfo libInfo, + @Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting) { + if (libInfo.getPath() != null) { + usesLibraryFiles.add(libInfo.getPath()); + return; + } + AndroidPackage pkgForCodePaths = mPm.mPackages.get(libInfo.getPackageName()); + PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(libInfo.getPackageName()); + if (changingLib != null && changingLib.getPackageName().equals(libInfo.getPackageName())) { + // If we are doing this while in the middle of updating a library apk, + // then we need to make sure to use that new apk for determining the + // dependencies here. (We haven't yet finished committing the new apk + // to the package manager state.) + if (pkgForCodePaths == null + || pkgForCodePaths.getPackageName().equals(changingLib.getPackageName())) { + pkgForCodePaths = changingLib; + pkgSetting = changingLibSetting; + } + } + if (pkgForCodePaths != null) { + usesLibraryFiles.addAll(AndroidPackageUtils.getAllCodePaths(pkgForCodePaths)); + // If the package provides libraries, add the dependency to them. + applyDefiningSharedLibraryUpdateLPr(pkg, libInfo, SharedLibraryInfo::addDependency); + if (pkgSetting != null) { + usesLibraryFiles.addAll(pkgSetting.getPkgState().getUsesLibraryFiles()); + } + } + } + + /** + * Collects all shared libraries being used by the target package. Rebuilds the dependencies + * of shared libraries and update the correct shared library code paths for it. + * + * @param pkg The target package to update shared library dependency. + * @param pkgSetting The target's package setting. + * @param changingLib The updating library package. + * @param changingLibSetting The updating library package setting. + * @param availablePackages All installed packages and current being installed packages. + */ + @GuardedBy("mPm.mLock") + void updateSharedLibrariesLPw(@NonNull AndroidPackage pkg, @NonNull PackageSetting pkgSetting, + @Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting, + @NonNull Map<String, AndroidPackage> availablePackages) + throws PackageManagerException { + final ArrayList<SharedLibraryInfo> sharedLibraryInfos = + SharedLibraryHelper.collectSharedLibraryInfos( + pkgSetting.getPkg(), availablePackages, mSharedLibraries, + null /* newLibraries */, mInjector.getCompatibility()); + executeSharedLibrariesUpdateLPw(pkg, pkgSetting, changingLib, changingLibSetting, + sharedLibraryInfos, mPm.mUserManager.getUserIds()); + } + + /** + * Rebuilds the dependencies of shared libraries for the target package, and update the + * shared library code paths to its package setting. + * + * @param pkg The target package to update shared library dependency. + * @param pkgSetting The target's package setting. + * @param changingLib The updating library package. + * @param changingLibSetting The updating library package setting. + * @param usesLibraryInfos The shared libraries used by the target package. + * @param allUsers All user ids on the device. + */ + @GuardedBy("mPm.mLock") + void executeSharedLibrariesUpdateLPw(AndroidPackage pkg, + @NonNull PackageSetting pkgSetting, @Nullable AndroidPackage changingLib, + @Nullable PackageSetting changingLibSetting, + ArrayList<SharedLibraryInfo> usesLibraryInfos, int[] allUsers) { + // If the package provides libraries, clear their old dependencies. + // This method will set them up again. + applyDefiningSharedLibraryUpdateLPr(pkg, null, (definingLibrary, dependency) -> { + definingLibrary.clearDependencies(); + }); + if (usesLibraryInfos != null) { + pkgSetting.getPkgState().setUsesLibraryInfos(usesLibraryInfos); + // Use LinkedHashSet to preserve the order of files added to + // usesLibraryFiles while eliminating duplicates. + Set<String> usesLibraryFiles = new LinkedHashSet<>(); + for (SharedLibraryInfo libInfo : usesLibraryInfos) { + addSharedLibraryLPr(pkg, usesLibraryFiles, libInfo, changingLib, + changingLibSetting); + } + pkgSetting.setPkgStateLibraryFiles(usesLibraryFiles); + + // let's make sure we mark all static shared libraries as installed for the same users + // that its dependent packages are installed for. + int[] installedUsers = new int[allUsers.length]; + int installedUserCount = 0; + for (int u = 0; u < allUsers.length; u++) { + if (pkgSetting.getInstalled(allUsers[u])) { + installedUsers[installedUserCount++] = allUsers[u]; + } + } + for (SharedLibraryInfo sharedLibraryInfo : usesLibraryInfos) { + if (!sharedLibraryInfo.isStatic()) { + continue; + } + final PackageSetting staticLibPkgSetting = + mPm.getPackageSettingForMutation(sharedLibraryInfo.getPackageName()); + if (staticLibPkgSetting == null) { + Slog.wtf(TAG, "Shared lib without setting: " + sharedLibraryInfo); + continue; + } + for (int u = 0; u < installedUserCount; u++) { + staticLibPkgSetting.setInstalled(true, installedUsers[u]); + } + } + } else { + pkgSetting.getPkgState().setUsesLibraryInfos(Collections.emptyList()) + .setUsesLibraryFiles(Collections.emptyList()); + } + } + + private static boolean hasString(List<String> list, List<String> which) { + if (list == null || which == null) { + return false; + } + for (int i = list.size() - 1; i >= 0; i--) { + for (int j = which.size() - 1; j >= 0; j--) { + if (which.get(j).equals(list.get(i))) { + return true; + } + } + } + return false; + } + + /** + * Update shared library dependencies and code paths for applications that are using the + * library {@code updatedPkg}. Update all applications if the {@code updatedPkg} is null. + * + * @param updatedPkg The updating shared library package. + * @param updatedPkgSetting The updating shared library package setting. + * @param availablePackages All available packages on the device. + * @return Packages that has been updated. + */ + @GuardedBy("mPm.mLock") + @Nullable ArrayList<AndroidPackage> updateAllSharedLibrariesLPw( + @Nullable AndroidPackage updatedPkg, @Nullable PackageSetting updatedPkgSetting, + @NonNull Map<String, AndroidPackage> availablePackages) { + ArrayList<AndroidPackage> resultList = null; + // Set of all descendants of a library; used to eliminate cycles + ArraySet<String> descendants = null; + // The current list of packages that need updating + List<Pair<AndroidPackage, PackageSetting>> needsUpdating = null; + if (updatedPkg != null && updatedPkgSetting != null) { + needsUpdating = new ArrayList<>(1); + needsUpdating.add(Pair.create(updatedPkg, updatedPkgSetting)); + } + do { + final Pair<AndroidPackage, PackageSetting> changingPkgPair = + (needsUpdating == null) ? null : needsUpdating.remove(0); + final AndroidPackage changingPkg = changingPkgPair != null + ? changingPkgPair.first : null; + final PackageSetting changingPkgSetting = changingPkgPair != null + ? changingPkgPair.second : null; + for (int i = mPm.mPackages.size() - 1; i >= 0; --i) { + final AndroidPackage pkg = mPm.mPackages.valueAt(i); + final PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName()); + if (changingPkg != null + && !hasString(pkg.getUsesLibraries(), changingPkg.getLibraryNames()) + && !hasString(pkg.getUsesOptionalLibraries(), changingPkg.getLibraryNames()) + && !ArrayUtils.contains(pkg.getUsesStaticLibraries(), + changingPkg.getStaticSharedLibName()) + && !ArrayUtils.contains(pkg.getUsesSdkLibraries(), + changingPkg.getSdkLibName())) { + continue; + } + if (resultList == null) { + resultList = new ArrayList<>(); + } + resultList.add(pkg); + // if we're updating a shared library, all of its descendants must be updated + if (changingPkg != null) { + if (descendants == null) { + descendants = new ArraySet<>(); + } + if (!descendants.contains(pkg.getPackageName())) { + descendants.add(pkg.getPackageName()); + needsUpdating.add(Pair.create(pkg, pkgSetting)); + } + } + try { + updateSharedLibrariesLPw(pkg, pkgSetting, changingPkg, + changingPkgSetting, availablePackages); + } catch (PackageManagerException e) { + // If a system app update or an app and a required lib missing we + // delete the package and for updated system apps keep the data as + // it is better for the user to reinstall than to be in an limbo + // state. Also libs disappearing under an app should never happen + // - just in case. + if (!pkg.isSystem() || pkgSetting.getPkgState().isUpdatedSystemApp()) { + final int flags = pkgSetting.getPkgState().isUpdatedSystemApp() + ? PackageManager.DELETE_KEEP_DATA : 0; + mDeletePackageHelper.deletePackageLIF(pkg.getPackageName(), null, true, + mPm.mUserManager.getUserIds(), flags, null, + true); + } + Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); + } + } + } while (needsUpdating != null && needsUpdating.size() > 0); + return resultList; + } + + /** + * Add a build-in shared library info by given system configuration. + */ + @GuardedBy("mPm.mLock") + void addBuiltInSharedLibraryLPw(@NonNull SystemConfig.SharedLibraryEntry entry) { + // check if built-in or dynamic library exists + if (getSharedLibraryInfo(entry.name, SharedLibraryInfo.VERSION_UNDEFINED) != null) { + return; + } + + SharedLibraryInfo libraryInfo = new SharedLibraryInfo(entry.filename, null, null, + entry.name, SharedLibraryInfo.VERSION_UNDEFINED, + SharedLibraryInfo.TYPE_BUILTIN, + new VersionedPackage(PLATFORM_PACKAGE_NAME, 0L), null, null, + entry.isNative); + + commitSharedLibraryInfoLPw(libraryInfo); + } + + /** + * Add a shared library info to the system. This is invoked when the package is being added or + * scanned. + */ + @GuardedBy("mPm.mLock") + void commitSharedLibraryInfoLPw(@NonNull SharedLibraryInfo libraryInfo) { + final String name = libraryInfo.getName(); + WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(name); + if (versionedLib == null) { + versionedLib = new WatchedLongSparseArray<>(); + mSharedLibraries.put(name, versionedLib); + } + final String declaringPackageName = libraryInfo.getDeclaringPackage().getPackageName(); + if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) { + mStaticLibsByDeclaringPackage.put(declaringPackageName, versionedLib); + } + versionedLib.put(libraryInfo.getLongVersion(), libraryInfo); + } + + /** + * Remove a shared library from the system. + */ + @GuardedBy("mPm.mLock") + boolean removeSharedLibraryLPw(@NonNull String libName, long version) { + WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName); + if (versionedLib == null) { + return false; + } + final int libIdx = versionedLib.indexOfKey(version); + if (libIdx < 0) { + return false; + } + SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx); + + // Remove the shared library overlays from its dependent packages. + for (int currentUserId : mPm.mUserManager.getUserIds()) { + final List<VersionedPackage> dependents = mPm.getPackagesUsingSharedLibrary( + libraryInfo, 0, Process.SYSTEM_UID, currentUserId); + if (dependents == null) { + continue; + } + for (VersionedPackage dependentPackage : dependents) { + final PackageSetting ps = mPm.mSettings.getPackageLPr( + dependentPackage.getPackageName()); + if (ps != null) { + ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId); + } + } + } + + versionedLib.remove(version); + if (versionedLib.size() <= 0) { + mSharedLibraries.remove(libName); + if (libraryInfo.getType() == SharedLibraryInfo.TYPE_STATIC) { + mStaticLibsByDeclaringPackage.remove(libraryInfo.getDeclaringPackage() + .getPackageName()); + } + } + return true; + } + + /** + * Dump all shared libraries. + */ + @GuardedBy("mPm.mLock") + @Override + public void dump(@NonNull PrintWriter pw, @NonNull DumpState dumpState) { + final boolean checkin = dumpState.isCheckIn(); + boolean printedHeader = false; + final int numSharedLibraries = mSharedLibraries.size(); + for (int index = 0; index < numSharedLibraries; index++) { + final String libName = mSharedLibraries.keyAt(index); + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + mSharedLibraries.get(libName); + if (versionedLib == null) { + continue; + } + final int versionCount = versionedLib.size(); + for (int i = 0; i < versionCount; i++) { + SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); + if (!checkin) { + if (!printedHeader) { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + pw.println("Libraries:"); + printedHeader = true; + } + pw.print(" "); + } else { + pw.print("lib,"); + } + pw.print(libraryInfo.getName()); + if (libraryInfo.isStatic()) { + pw.print(" version=" + libraryInfo.getLongVersion()); + } + if (!checkin) { + pw.print(" -> "); + } + if (libraryInfo.getPath() != null) { + if (libraryInfo.isNative()) { + pw.print(" (so) "); + } else { + pw.print(" (jar) "); + } + pw.print(libraryInfo.getPath()); + } else { + pw.print(" (apk) "); + pw.print(libraryInfo.getPackageName()); + } + pw.println(); + } + } + } + + /** + * Dump all shared libraries to given proto output stream. + */ + @GuardedBy("mPm.mLock") + @Override + public void dumpProto(@NonNull ProtoOutputStream proto) { + final int count = mSharedLibraries.size(); + for (int i = 0; i < count; i++) { + final String libName = mSharedLibraries.keyAt(i); + WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + mSharedLibraries.get(libName); + if (versionedLib == null) { + continue; + } + final int versionCount = versionedLib.size(); + for (int j = 0; j < versionCount; j++) { + final SharedLibraryInfo libraryInfo = versionedLib.valueAt(j); + final long sharedLibraryToken = + proto.start(PackageServiceDumpProto.SHARED_LIBRARIES); + proto.write(PackageServiceDumpProto.SharedLibraryProto.NAME, libraryInfo.getName()); + final boolean isJar = (libraryInfo.getPath() != null); + proto.write(PackageServiceDumpProto.SharedLibraryProto.IS_JAR, isJar); + if (isJar) { + proto.write(PackageServiceDumpProto.SharedLibraryProto.PATH, + libraryInfo.getPath()); + } else { + proto.write(PackageServiceDumpProto.SharedLibraryProto.APK, + libraryInfo.getPackageName()); + } + proto.end(sharedLibraryToken); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/SharedLibrariesRead.java b/services/core/java/com/android/server/pm/SharedLibrariesRead.java new file mode 100644 index 000000000000..e6f231117b32 --- /dev/null +++ b/services/core/java/com/android/server/pm/SharedLibrariesRead.java @@ -0,0 +1,86 @@ +/* + * 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.server.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.SharedLibraryInfo; +import android.util.proto.ProtoOutputStream; + +import com.android.server.utils.WatchedArrayMap; +import com.android.server.utils.WatchedLongSparseArray; + +import java.io.PrintWriter; + +/** + * An interface implemented by {@link SharedLibrariesImpl} for {@link Computer} to get current + * shared libraries on the device. + */ +interface SharedLibrariesRead { + + /** + * Returns all shared libraries on the device. + * + * @return A map of library name to a list of {@link SharedLibraryInfo}s with + * different versions. + */ + @NonNull + WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> getAll(); + + /** + * Given the library name, returns a list of shared libraries on all versions. + * + * @param libName The library name. + * @return A list of shared library info. + */ + @Nullable + WatchedLongSparseArray<SharedLibraryInfo> getSharedLibraryInfos(@NonNull String libName); + + /** + * Returns the shared library with given library name and version number. + * + * @param libName The library name. + * @param version The library version number. + * @return The shared library info. + */ + @Nullable + SharedLibraryInfo getSharedLibraryInfo(@NonNull String libName, long version); + + /** + * Given the declaring package name, returns a list of static shared libraries on all versions. + * + * @param declaringPackageName The declaring name of the package. + * @return A list of shared library info. + */ + @Nullable + WatchedLongSparseArray<SharedLibraryInfo> getStaticLibraryInfos( + @NonNull String declaringPackageName); + + /** + * Dump all shared libraries. + * + * @param pw A PrintWriter to dump to. + * @param dumpState Including options and states for writing. + */ + void dump(@NonNull PrintWriter pw, @NonNull DumpState dumpState); + + /** + * Dump all shared libraries to given proto output stream. + * @param proto A proto output stream to dump to. + */ + void dumpProto(@NonNull ProtoOutputStream proto); +} diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 3d10b6f06488..bf7ef1b24776 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -22,6 +22,7 @@ import android.app.Person; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSession; +import android.app.appsearch.GetByDocumentIdRequest; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.PutDocumentsRequest; import android.app.appsearch.RemoveByDocumentIdRequest; @@ -820,42 +821,6 @@ class ShortcutPackage extends ShortcutPackageItem { getPinnedByAnyLauncher, si)); } - /** - * Find all shortcuts that has id matching {@code ids}. - */ - public void findAllByIds(@NonNull final List<ShortcutInfo> result, - @NonNull final Collection<String> ids, @Nullable final Predicate<ShortcutInfo> filter, - final int cloneFlag) { - findAllByIds(result, ids, filter, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false); - } - - /** - * Find all shortcuts that has id matching {@code ids}. - * - * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned - * by the calling launcher will not be included in the result, and also "isPinned" will be - * adjusted for the caller too. - */ - public void findAllByIds(@NonNull List<ShortcutInfo> result, - @NonNull final Collection<String> ids, @Nullable final Predicate<ShortcutInfo> query, - int cloneFlag, @Nullable String callingLauncher, int launcherUserId, - boolean getPinnedByAnyLauncher) { - if (getPackageInfo().isShadow()) { - // Restored and the app not installed yet, so don't return any. - return; - } - final ShortcutService s = mShortcutUser.mService; - - // Set of pinned shortcuts by the calling launcher. - final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null - : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) - .getPinnedShortcutIds(getPackageName(), getPackageUserId()); - for (ShortcutInfo si : mShortcuts.values()) { - filter(result, query, cloneFlag, callingLauncher, pinnedByCallerSet, - getPinnedByAnyLauncher, si); - } - } - private void filter(@NonNull final List<ShortcutInfo> result, @Nullable final Predicate<ShortcutInfo> query, final int cloneFlag, @Nullable final String callingLauncher, @@ -2411,6 +2376,25 @@ class ShortcutPackage extends ShortcutPackageItem { }))); } + void getShortcutByIdsAsync(@NonNull final Set<String> ids, + @NonNull final Consumer<List<ShortcutInfo>> cb) { + if (!isAppSearchEnabled()) { + cb.accept(Collections.emptyList()); + return; + } + runAsSystem(() -> fromAppSearch().thenAccept(session -> { + session.getByDocumentId(new GetByDocumentIdRequest.Builder(getPackageName()) + .addIds(ids).build(), mShortcutUser.mExecutor, result -> { + final List<ShortcutInfo> ret = result.getSuccesses().values() + .stream().map(doc -> + new AppSearchShortcutInfo(doc) + .toShortcutInfo(mShortcutUser.getUserId())) + .collect(Collectors.toList()); + cb.accept(ret); + }); + })); + } + private void removeShortcutAsync(@NonNull final String... id) { Objects.requireNonNull(id); removeShortcutAsync(Arrays.asList(id)); @@ -2444,9 +2428,8 @@ class ShortcutPackage extends ShortcutPackageItem { } if (ShortcutService.DEBUG_REBOOT) { Slog.d(TAG, "Saving shortcuts async for user=" + mShortcutUser.getUserId() - + " pkg=" + getPackageName() + " ids=[" - + shortcuts.stream().map(ShortcutInfo::getId) - .collect(Collectors.joining(",")) + "]"); + + " pkg=" + getPackageName() + " ids=" + shortcuts.stream() + .map(ShortcutInfo::getId).collect(Collectors.joining(",", "[", "]"))); } runAsSystem(() -> fromAppSearch().thenAccept(session -> { if (shortcuts.isEmpty()) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index a482f9a619ba..0a2735cdbf76 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -153,6 +153,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * TODO: @@ -2957,13 +2958,8 @@ public class ShortcutService extends IShortcutService.Stub { final Predicate<ShortcutInfo> filter = getFilterFromQuery(ids, locusIds, changedSince, componentName, queryFlags, getPinnedByAnyLauncher); - if (ids != null && !ids.isEmpty()) { - p.findAllByIds(ret, ids, filter, cloneFlag, callingPackage, launcherUserId, + p.findAll(ret, filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); - } else { - p.findAll(ret, filter, cloneFlag, callingPackage, launcherUserId, - getPinnedByAnyLauncher); - } } private Predicate<ShortcutInfo> getFilterFromQuery(@Nullable ArraySet<String> ids, @@ -3010,6 +3006,51 @@ public class ShortcutService extends IShortcutService.Stub { } @Override + public void getShortcutsAsync(int launcherUserId, + @NonNull String callingPackage, long changedSince, + @Nullable String packageName, @Nullable List<String> shortcutIds, + @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, + int queryFlags, int userId, int callingPid, int callingUid, + @NonNull AndroidFuture<List<ShortcutInfo>> cb) { + final List<ShortcutInfo> ret = getShortcuts(launcherUserId, callingPackage, + changedSince, packageName, shortcutIds, locusIds, componentName, queryFlags, + userId, callingPid, callingUid); + if (shortcutIds == null || packageName == null || ret.size() >= shortcutIds.size()) { + // skip persistence layer if not querying by id in a specific package or all + // shortcuts have already been found. + cb.complete(ret); + return; + } + final ShortcutPackage p; + synchronized (mLock) { + p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); + } + if (p == null) { + cb.complete(ret); + return; // Bail-out directly if package doesn't exist. + } + // fetch remaining shortcuts from persistence layer + final ArraySet<String> ids = new ArraySet<>(shortcutIds); + // remove the ids that are already fetched + ret.stream().map(ShortcutInfo::getId).collect(Collectors.toList()).forEach(ids::remove); + + int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; + if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) { + flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; + } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { + flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON; + } + final int cloneFlag = flags; + + p.getShortcutByIdsAsync(ids, shortcuts -> { + if (shortcuts != null) { + shortcuts.stream().map(si -> si.clone(cloneFlag)).forEach(ret::add); + } + cb.complete(ret); + }); + } + + @Override public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId) { Preconditions.checkStringNotEmpty(packageName, "packageName"); @@ -3047,12 +3088,32 @@ public class ShortcutService extends IShortcutService.Stub { } final ArrayList<ShortcutInfo> list = new ArrayList<>(1); - p.findAllByIds(list, Collections.singletonList(shortcutId), - (ShortcutInfo si) -> shortcutId.equals(si.getId()), + p.findAll(list, (ShortcutInfo si) -> shortcutId.equals(si.getId()), /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher); return list.size() == 0 ? null : list.get(0); } + private void getShortcutInfoAsync( + int launcherUserId, @NonNull String packageName, @NonNull String shortcutId, + int userId, @NonNull Consumer<ShortcutInfo> cb) { + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); + + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + final ShortcutPackage p; + synchronized (mLock) { + p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); + } + if (p == null) { + cb.accept(null); + return; + } + p.getShortcutByIdsAsync(Collections.singleton(shortcutId), shortcuts -> + cb.accept(shortcuts == null || shortcuts.isEmpty() ? null : shortcuts.get(0))); + } + @Override public void pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @@ -3237,6 +3298,48 @@ public class ShortcutService extends IShortcutService.Stub { } @Override + public void createShortcutIntentsAsync(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull String shortcutId, int userId, int callingPid, + int callingUid, @NonNull AndroidFuture<Intent[]> cb) { + // Calling permission must be checked by LauncherAppsImpl. + Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); + Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); + + // Check in memory shortcut first + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(); + + final boolean getPinnedByAnyLauncher = + canSeeAnyPinnedShortcut(callingPackage, launcherUserId, + callingPid, callingUid); + + // Make sure the shortcut is actually visible to the launcher. + final ShortcutInfo si = getShortcutInfoLocked( + launcherUserId, callingPackage, packageName, shortcutId, userId, + getPinnedByAnyLauncher); + if (si != null) { + if (!si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) { + Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); + cb.complete(null); + return; + } + cb.complete(si.getIntents()); + return; + } + } + + // Otherwise check persisted shortcuts + getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { + cb.complete(si == null ? null : si.getIntents()); + }); + } + + @Override public void addListener(@NonNull ShortcutChangeListener listener) { synchronized (mLock) { mListeners.add(Objects.requireNonNull(listener)); @@ -3326,23 +3429,68 @@ public class ShortcutService extends IShortcutService.Stub { } final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); - if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { + if (shortcutInfo == null) { return null; } - final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); - if (path == null) { - Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); - return null; + return getShortcutIconParcelFileDescriptor(shortcutInfo); + } + } + + @Override + public void getShortcutIconFdAsync(int launcherUserId, @NonNull String callingPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull AndroidFuture<ParcelFileDescriptor> cb) { + Objects.requireNonNull(callingPackage, "callingPackage"); + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(shortcutId, "shortcutId"); + + // Checks shortcuts in memory first + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(); + + final ShortcutPackage p = getUserShortcutsLocked(userId) + .getPackageShortcutsIfExists(packageName); + if (p == null) { + cb.complete(null); + return; } - try { - return ParcelFileDescriptor.open( - new File(path), - ParcelFileDescriptor.MODE_READ_ONLY); - } catch (FileNotFoundException e) { - Slog.e(TAG, "Icon file not found: " + path); - return null; + + final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); + if (shortcutInfo != null) { + cb.complete(getShortcutIconParcelFileDescriptor(shortcutInfo)); + return; } } + + // Otherwise check persisted shortcuts + getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { + cb.complete(getShortcutIconParcelFileDescriptor(si)); + }); + } + + @Nullable + private ParcelFileDescriptor getShortcutIconParcelFileDescriptor( + @NonNull final ShortcutInfo shortcutInfo) { + if (!shortcutInfo.hasIconFile()) { + return null; + } + final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); + if (path == null) { + Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); + return null; + } + try { + return ParcelFileDescriptor.open( + new File(path), + ParcelFileDescriptor.MODE_READ_ONLY); + } catch (FileNotFoundException e) { + Slog.e(TAG, "Icon file not found: " + path); + return null; + } } @Override @@ -3366,34 +3514,82 @@ public class ShortcutService extends IShortcutService.Stub { } final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); - if (shortcutInfo == null || !shortcutInfo.hasIconUri()) { + if (shortcutInfo == null) { return null; } - String uri = shortcutInfo.getIconUri(); - if (uri == null) { - Slog.w(TAG, "null uri detected in getShortcutIconUri()"); - return null; + return getShortcutIconUriInternal(launcherUserId, launcherPackage, + packageName, shortcutInfo, userId); + } + } + + @Override + public void getShortcutIconUriAsync(int launcherUserId, @NonNull String launcherPackage, + @NonNull String packageName, @NonNull String shortcutId, int userId, + @NonNull AndroidFuture<String> cb) { + Objects.requireNonNull(launcherPackage, "launcherPackage"); + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(shortcutId, "shortcutId"); + + // Checks shortcuts in memory first + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + getLauncherShortcutsLocked(launcherPackage, userId, launcherUserId) + .attemptToRestoreIfNeededAndSave(); + + final ShortcutPackage p = getUserShortcutsLocked(userId) + .getPackageShortcutsIfExists(packageName); + if (p == null) { + cb.complete(null); + return; } - final long token = Binder.clearCallingIdentity(); - try { - int packageUid = mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); - // Grant read uri permission to the caller on behalf of the shortcut owner. All - // granted permissions are revoked when the default launcher changes, or when - // device is rebooted. - mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid, - launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, - userId, launcherUserId); - } catch (Exception e) { - Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri, - e); - uri = null; - } finally { - Binder.restoreCallingIdentity(token); + final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); + if (shortcutInfo != null) { + cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage, + packageName, shortcutInfo, userId)); + return; } - return uri; } + + // Otherwise check persisted shortcuts + getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { + cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage, + packageName, si, userId)); + }); + } + + private String getShortcutIconUriInternal(int launcherUserId, + @NonNull String launcherPackage, @NonNull String packageName, + @NonNull ShortcutInfo shortcutInfo, int userId) { + if (!shortcutInfo.hasIconUri()) { + return null; + } + String uri = shortcutInfo.getIconUri(); + if (uri == null) { + Slog.w(TAG, "null uri detected in getShortcutIconUri()"); + return null; + } + + final long token = Binder.clearCallingIdentity(); + try { + int packageUid = mPackageManagerInternal.getPackageUid(packageName, + PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); + // Grant read uri permission to the caller on behalf of the shortcut owner. All + // granted permissions are revoked when the default launcher changes, or when + // device is rebooted. + mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid, + launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, + userId, launcherUserId); + } catch (Exception e) { + Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri, + e); + uri = null; + } finally { + Binder.restoreCallingIdentity(token); + } + return uri; } @Override @@ -5154,7 +5350,7 @@ public class ShortcutService extends IShortcutService.Stub { } List<ShortcutInfo> result = new ArrayList<>(); - ps.findAllByIds(result, resultIds, (ShortcutInfo si) -> resultIds.contains(si.getId()), + ps.findAll(result, (ShortcutInfo si) -> resultIds.contains(si.getId()), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); return result; } diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 9cb886341566..8a6ef6bfcb41 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -32,7 +32,7 @@ import android.content.pm.ApexStagedEvent; import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; +import android.content.pm.PackageInstaller.SessionInfo.SessionErrorCode; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.StagedApexInfo; @@ -129,7 +129,7 @@ public class StagingManager { boolean containsApkSession(); boolean containsApexSession(); void setSessionReady(); - void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage); + void setSessionFailed(@SessionErrorCode int errorCode, String errorMessage); void setSessionApplied(); void installSession(IntentSender statusReceiver); boolean hasParentSessionId(); @@ -932,9 +932,7 @@ public class StagingManager { info.diskImagePath = ai.modulePath; info.versionCode = ai.versionCode; info.versionName = ai.versionName; - info.hasBootClassPathJars = ai.hasBootClassPathJars; - info.hasDex2OatBootClassPathJars = ai.hasDex2OatBootClassPathJars; - info.hasSystemServerClassPathJars = ai.hasSystemServerClassPathJars; + info.hasClassPathJars = ai.hasClassPathJars; return info; } } 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 a4f8087a2a07..b15e495a3714 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -810,7 +810,7 @@ final class DefaultPermissionGrantPolicy { } else { grantPermissionsToSystemPackage(pm, getDefaultSystemHandlerActivityPackage(pm, ACTION_TRACK, userId), userId, - SENSORS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS); + SENSORS_PERMISSIONS); } } diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 34575e081ca8..f5ee8d9d4569 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -100,12 +100,6 @@ public interface PackageState { String getCpuAbiOverride(); /** - * In epoch milliseconds. The timestamp of the first install of the particular app on the - * device, surviving past app updates. This does not survive full uninstalls + reinstalls. - */ - long getFirstInstallTime(); - - /** * In epoch milliseconds. The last modified time of the file directory which houses the app * APKs. Only updated on package update; does not track realtime modifications. */ diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java index f5e498d2fc9a..a5d399e18f0c 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java @@ -115,7 +115,6 @@ public class PackageStateImpl implements PackageState { private final int mCategoryOverride; @Nullable private final String mCpuAbiOverride; - private final long mFirstInstallTime; private final long mLastModifiedTime; private final long mLastUpdateTime; private final long mLongVersionCode; @@ -166,7 +165,6 @@ public class PackageStateImpl implements PackageState { mAppId = pkgState.getAppId(); mCategoryOverride = pkgState.getCategoryOverride(); mCpuAbiOverride = pkgState.getCpuAbiOverride(); - mFirstInstallTime = pkgState.getFirstInstallTime(); mLastModifiedTime = pkgState.getLastModifiedTime(); mLastUpdateTime = pkgState.getLastUpdateTime(); mLongVersionCode = pkgState.getVersionCode(); @@ -341,6 +339,7 @@ public class PackageStateImpl implements PackageState { private final int mUninstallReason; @Nullable private final String mSplashScreenTheme; + private final long mFirstInstallTime; private UserStateImpl(@NonNull PackageUserState userState) { mCeDataInode = userState.getCeDataInode(); @@ -362,6 +361,7 @@ public class PackageStateImpl implements PackageState { setBoolean(Booleans.STOPPED, userState.isStopped()); setBoolean(Booleans.SUSPENDED, userState.isSuspended()); setBoolean(Booleans.VIRTUAL_PRELOAD, userState.isVirtualPreload()); + mFirstInstallTime = userState.getFirstInstallTime(); } @Override @@ -505,16 +505,21 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member + public long getFirstInstallTime() { + return mFirstInstallTime; + } + + @DataClass.Generated.Member public @NonNull UserStateImpl setBooleans( int value) { mBooleans = value; return this; } @DataClass.Generated( - time = 1637977288540L, + time = 1640209608883L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") + inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull java.util.Set<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @Deprecated private void __metadata() {} @@ -575,11 +580,6 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member - public long getFirstInstallTime() { - return mFirstInstallTime; - } - - @DataClass.Generated.Member public long getLastModifiedTime() { return mLastModifiedTime; } @@ -671,10 +671,10 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1637977288579L, + time = 1640209608912L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackageApi mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mFirstInstallTime\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.Integer mSharedUserId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") + inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackageApi mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.Integer mSharedUserId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mUsesLibraryInfos\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java index 5460afa665e6..56f62ab87933 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java @@ -23,6 +23,7 @@ import android.content.pm.SigningDetails; import android.util.SparseArray; import com.android.server.pm.InstallSource; +import com.android.server.pm.PackageKeySetData; import com.android.server.pm.SharedUserSetting; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.LegacyPermissionState; @@ -82,4 +83,7 @@ public interface PackageStateInternal extends PackageState { String getPathString(); float getLoadingProgress(); + + @NonNull + PackageKeySetData getKeySetData(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index 09b9d31e2fe2..6c8e0b72198d 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -22,6 +22,7 @@ import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.parsing.component.ParsedMainComponent; import android.content.pm.pkg.PackageUserStateUtils; +import android.util.SparseArray; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -75,4 +76,23 @@ public class PackageStateUtils { return PackageUserStateUtils.isMatch(userState, packageState.isSystem(), pkg.isEnabled(), component, flags); } + + /** + * Return the earliest non-zero first-install timestamp of an installed app among all the users, + * unless none of the users have a non-zero first-install timestamp. In that case, return 0. + */ + public static long getEarliestFirstInstallTime( + @Nullable SparseArray<? extends PackageUserStateInternal> userStatesInternal) { + if (userStatesInternal == null || userStatesInternal.size() == 0) { + return 0; + } + long earliestFirstInstallTime = Long.MAX_VALUE; + for (int i = 0; i < userStatesInternal.size(); i++) { + final long firstInstallTime = userStatesInternal.valueAt(i).getFirstInstallTime(); + if (firstInstallTime != 0 && firstInstallTime < earliestFirstInstallTime) { + earliestFirstInstallTime = firstInstallTime; + } + } + return earliestFirstInstallTime == Long.MAX_VALUE ? 0 : earliestFirstInstallTime; + } } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 147edf745b05..03b16929bed8 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -150,4 +150,13 @@ public interface PackageUserState extends FrameworkPackageUserState { */ @Nullable String getSplashScreenTheme(); + + /** + * In epoch milliseconds. The timestamp of the first install of the app of the particular user + * on the device, surviving past app updates. Different users might have a different first + * install time. + * + * This does not survive full removal of the app (i.e., uninstalls for all users). + */ + long getFirstInstallTime(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 481c3e062091..73c86c7f163d 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -134,6 +134,11 @@ class PackageUserStateDefault implements PackageUserStateInternal { } @Override + public long getFirstInstallTime() { + return 0; + } + + @Override public boolean isComponentEnabled(String componentName) { return false; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index 32a9cf1bdb3d..25abcb3bec35 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -28,6 +28,7 @@ import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import java.util.Objects; @@ -49,7 +50,6 @@ public class PackageUserStateImpl implements PackageUserStateInternal { private boolean mNotLaunched; private boolean mHidden; // Is the app restricted by owner / admin private int mDistractionFlags; - private boolean mSuspended; private boolean mInstantApp; private boolean mVirtualPreload; private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; @@ -82,6 +82,8 @@ public class PackageUserStateImpl implements PackageUserStateInternal { @Nullable private ArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap; + private long mFirstInstallTime; + public PackageUserStateImpl() { super(); } @@ -101,7 +103,6 @@ public class PackageUserStateImpl implements PackageUserStateInternal { mNotLaunched = other.mNotLaunched; mHidden = other.mHidden; mDistractionFlags = other.mDistractionFlags; - mSuspended = other.mSuspended; mInstantApp = other.mInstantApp; mVirtualPreload = other.mVirtualPreload; mEnabledState = other.mEnabledState; @@ -256,6 +257,28 @@ public class PackageUserStateImpl implements PackageUserStateInternal { return mComponentLabelIconOverrideMap.get(componentName); } + @Override + public boolean isSuspended() { + return !CollectionUtils.isEmpty(mSuspendParams); + } + + public PackageUserStateImpl putSuspendParams(@NonNull String suspendingPackage, + @NonNull SuspendParams suspendParams) { + if (mSuspendParams == null) { + mSuspendParams = new ArrayMap<>(); + } + mSuspendParams.put(suspendingPackage, suspendParams); + return this; + } + + public PackageUserStateImpl removeSuspension(@NonNull String suspendingPackage) { + if (mSuspendParams != null) { + mSuspendParams.remove(suspendingPackage); + } + return this; + } + + // Code below generated by codegen v1.0.23. @@ -312,11 +335,6 @@ public class PackageUserStateImpl implements PackageUserStateInternal { } @DataClass.Generated.Member - public boolean isSuspended() { - return mSuspended; - } - - @DataClass.Generated.Member public boolean isInstantApp() { return mInstantApp; } @@ -385,6 +403,11 @@ public class PackageUserStateImpl implements PackageUserStateInternal { } @DataClass.Generated.Member + public long getFirstInstallTime() { + return mFirstInstallTime; + } + + @DataClass.Generated.Member public @NonNull PackageUserStateImpl setDisabledComponents(@NonNull ArraySet<String> value) { mDisabledComponents = value; return this; @@ -433,12 +456,6 @@ public class PackageUserStateImpl implements PackageUserStateInternal { } @DataClass.Generated.Member - public @NonNull PackageUserStateImpl setSuspended( boolean value) { - mSuspended = value; - return this; - } - - @DataClass.Generated.Member public @NonNull PackageUserStateImpl setInstantApp( boolean value) { mInstantApp = value; return this; @@ -511,6 +528,12 @@ public class PackageUserStateImpl implements PackageUserStateInternal { return this; } + @DataClass.Generated.Member + public @NonNull PackageUserStateImpl setFirstInstallTime( long value) { + mFirstInstallTime = value; + return this; + } + @Override @DataClass.Generated.Member public boolean equals(@Nullable Object o) { @@ -532,7 +555,6 @@ public class PackageUserStateImpl implements PackageUserStateInternal { && mNotLaunched == that.mNotLaunched && mHidden == that.mHidden && mDistractionFlags == that.mDistractionFlags - && mSuspended == that.mSuspended && mInstantApp == that.mInstantApp && mVirtualPreload == that.mVirtualPreload && mEnabledState == that.mEnabledState @@ -545,7 +567,8 @@ public class PackageUserStateImpl implements PackageUserStateInternal { && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme) && Objects.equals(mSuspendParams, that.mSuspendParams) && Objects.equals(mCachedOverlayPaths, that.mCachedOverlayPaths) - && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap); + && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap) + && mFirstInstallTime == that.mFirstInstallTime; } @Override @@ -563,7 +586,6 @@ public class PackageUserStateImpl implements PackageUserStateInternal { _hash = 31 * _hash + Boolean.hashCode(mNotLaunched); _hash = 31 * _hash + Boolean.hashCode(mHidden); _hash = 31 * _hash + mDistractionFlags; - _hash = 31 * _hash + Boolean.hashCode(mSuspended); _hash = 31 * _hash + Boolean.hashCode(mInstantApp); _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload); _hash = 31 * _hash + mEnabledState; @@ -577,14 +599,15 @@ public class PackageUserStateImpl implements PackageUserStateInternal { _hash = 31 * _hash + Objects.hashCode(mSuspendParams); _hash = 31 * _hash + Objects.hashCode(mCachedOverlayPaths); _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap); + _hash = 31 * _hash + Long.hashCode(mFirstInstallTime); return _hash; } @DataClass.Generated( - time = 1633983318771L, + time = 1640923839971L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "protected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mDisabledComponents\nprotected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mSuspended\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprotected @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mCachedOverlayPaths\nprivate @android.annotation.Nullable android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\nclass PackageUserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserStateInternal]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "protected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mDisabledComponents\nprotected @android.annotation.Nullable android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprotected @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mCachedOverlayPaths\nprivate @android.annotation.Nullable android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\nclass PackageUserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserStateInternal]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java index 6f3331223bff..bd8b3ab61140 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java @@ -32,6 +32,7 @@ public interface PackageUserStateInternal extends PackageUserState { PackageUserStateInternal DEFAULT = new PackageUserStateDefault(); + // TODO: Make non-null with emptyMap() @Nullable ArrayMap<String, SuspendParams> getSuspendParams(); diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java index 71512dc84088..d24ce96bb4b6 100644 --- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java +++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java @@ -42,11 +42,15 @@ public final class SuspendParams { private static final String TAG_APP_EXTRAS = "app-extras"; private static final String TAG_LAUNCHER_EXTRAS = "launcher-extras"; - public SuspendDialogInfo dialogInfo; - public PersistableBundle appExtras; - public PersistableBundle launcherExtras; + private final SuspendDialogInfo dialogInfo; + private final PersistableBundle appExtras; + private final PersistableBundle launcherExtras; - private SuspendParams() { + private SuspendParams(SuspendDialogInfo dialogInfo, PersistableBundle appExtras, + PersistableBundle launcherExtras) { + this.dialogInfo = dialogInfo; + this.appExtras = appExtras; + this.launcherExtras = launcherExtras; } /** @@ -60,11 +64,7 @@ public final class SuspendParams { if (dialogInfo == null && appExtras == null && launcherExtras == null) { return null; } - final SuspendParams instance = new SuspendParams(); - instance.dialogInfo = dialogInfo; - instance.appExtras = appExtras; - instance.launcherExtras = launcherExtras; - return instance; + return new SuspendParams(dialogInfo, appExtras, launcherExtras); } @Override @@ -172,4 +172,16 @@ public final class SuspendParams { } return getInstanceOrNull(readDialogInfo, readAppExtras, readLauncherExtras); } + + public SuspendDialogInfo getDialogInfo() { + return dialogInfo; + } + + public PersistableBundle getAppExtras() { + return appExtras; + } + + public PersistableBundle getLauncherExtras() { + return launcherExtras; + } } diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java new file mode 100644 index 000000000000..35d4d9e82fbe --- /dev/null +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java @@ -0,0 +1,333 @@ +/* + * 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.server.pm.pkg.mutate; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.overlay.OverlayPaths; +import android.util.ArraySet; + +import com.android.server.pm.PackageSetting; +import com.android.server.pm.pkg.PackageUserStateImpl; +import com.android.server.pm.pkg.SuspendParams; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +public class PackageStateMutator { + + private static final AtomicLong sStateChangeSequence = new AtomicLong(); + + private final StateWriteWrapper mStateWrite = new StateWriteWrapper(); + + private final Function<String, PackageSetting> mActiveStateFunction; + private final Function<String, PackageSetting> mDisabledStateFunction; + + public PackageStateMutator(@NonNull Function<String, PackageSetting> activeStateFunction, + @NonNull Function<String, PackageSetting> disabledStateFunction) { + mActiveStateFunction = activeStateFunction; + mDisabledStateFunction = disabledStateFunction; + } + + public static void onPackageStateChanged() { + sStateChangeSequence.incrementAndGet(); + } + + @NonNull + public PackageStateWrite forPackage(@NonNull String packageName) { + return mStateWrite.setState(mActiveStateFunction.apply(packageName)); + } + + @Nullable + public PackageStateWrite forPackageNullable(@NonNull String packageName) { + final PackageSetting packageState = mActiveStateFunction.apply(packageName); + mStateWrite.setState(packageState); + if (packageState == null) { + return null; + } + + return mStateWrite.setState(packageState); + } + + @NonNull + public PackageStateWrite forDisabledSystemPackage(@NonNull String packageName) { + return mStateWrite.setState(mDisabledStateFunction.apply(packageName)); + } + + @Nullable + public PackageStateWrite forDisabledSystemPackageNullable(@NonNull String packageName) { + final PackageSetting packageState = mDisabledStateFunction.apply(packageName); + if (packageState == null) { + return null; + } + + return mStateWrite.setState(packageState); + } + + @NonNull + public InitialState initialState(int changedPackagesSequenceNumber) { + return new InitialState(changedPackagesSequenceNumber, sStateChangeSequence.get()); + } + + /** + * @return null if initial state is null or if nothing has changed, otherwise return result + * with what changed + */ + @Nullable + public Result generateResult(@Nullable InitialState state, int changedPackagesSequenceNumber) { + if (state == null) { + return Result.SUCCESS; + } + + boolean packagesChanged = changedPackagesSequenceNumber != state.mPackageSequence; + boolean stateChanged = sStateChangeSequence.get() != state.mStateSequence; + if (packagesChanged && stateChanged) { + return Result.PACKAGES_AND_STATE_CHANGED; + } else if (packagesChanged) { + return Result.PACKAGES_CHANGED; + } else if (stateChanged) { + return Result.STATE_CHANGED; + } else { + return Result.SUCCESS; + } + } + + public static class InitialState { + + private final int mPackageSequence; + private final long mStateSequence; + + public InitialState(int packageSequence, long stateSequence) { + mPackageSequence = packageSequence; + mStateSequence = stateSequence; + } + } + + public static class Result { + + public static final Result SUCCESS = new Result(true, false, false, false); + public static final Result PACKAGES_CHANGED = new Result(false, true, false, false); + public static final Result STATE_CHANGED = new Result(false, false, true, false); + public static final Result PACKAGES_AND_STATE_CHANGED = new Result(false, true, true, false); + public static final Result SPECIFIC_PACKAGE_NULL = new Result(false, false, true, true); + + private final boolean mCommitted; + private final boolean mPackagesChanged; + private final boolean mStateChanged; + private final boolean mSpecificPackageNull; + + public Result(boolean committed, boolean packagesChanged, boolean stateChanged, + boolean specificPackageNull) { + mCommitted = committed; + mPackagesChanged = packagesChanged; + mStateChanged = stateChanged; + mSpecificPackageNull = specificPackageNull; + } + + public boolean isCommitted() { + return mCommitted; + } + + public boolean isPackagesChanged() { + return mPackagesChanged; + } + + public boolean isStateChanged() { + return mStateChanged; + } + + public boolean isSpecificPackageNull() { + return mSpecificPackageNull; + } + } + + private static class StateWriteWrapper implements PackageStateWrite { + + private final UserStateWriteWrapper mUserStateWrite = new UserStateWriteWrapper(); + + @NonNull + private PackageSetting mState; + + public StateWriteWrapper setState(PackageSetting state) { + this.mState = state; + return this; + } + + @NonNull + @Override + public PackageUserStateWrite userState(int userId) { + return mUserStateWrite.setStates( + mState == null ? null : mState.getOrCreateUserState(userId)); + } + + @Override + public PackageStateWrite setLastPackageUsageTime(int reason, long timeInMillis) { + if (mState != null) { + mState.getTransientState().setLastPackageUsageTimeInMills(reason, timeInMillis); + } + return this; + } + + @Override + public PackageStateWrite setHiddenUntilInstalled(boolean value) { + if (mState != null) { + mState.getTransientState().setHiddenUntilInstalled(value); + } + return this; + } + + @NonNull + @Override + public PackageStateWrite setRequiredForSystemUser(boolean requiredForSystemUser) { + if (mState != null) { + if (requiredForSystemUser) { + mState.setPrivateFlags(mState.getPrivateFlags() + | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER); + } else { + mState.setPrivateFlags(mState.getPrivateFlags() + & ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER); + } + } + return this; + } + + @NonNull + @Override + public PackageStateWrite setMimeGroup(@NonNull String mimeGroup, + @NonNull ArraySet<String> mimeTypes) { + if (mState != null) { + mState.setMimeGroup(mimeGroup, mimeTypes); + } + return this; + } + + private static class UserStateWriteWrapper implements PackageUserStateWrite { + + @Nullable + private PackageUserStateImpl mUserState; + + public UserStateWriteWrapper setStates(@Nullable PackageUserStateImpl userState) { + mUserState = userState; + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setInstalled(boolean installed) { + if (mUserState != null) { + mUserState.setInstalled(installed); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setUninstallReason(int reason) { + if (mUserState != null) { + mUserState.setUninstallReason(reason); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setDistractionFlags( + @PackageManager.DistractionRestriction int restrictionFlags) { + if (mUserState != null) { + mUserState.setDistractionFlags(restrictionFlags); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage, + @Nullable SuspendParams suspendParams) { + if (mUserState != null) { + mUserState.putSuspendParams(suspendingPackage, suspendParams); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage) { + if (mUserState != null) { + mUserState.removeSuspension(suspendingPackage); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setHidden(boolean hidden) { + if (mUserState != null) { + mUserState.setHidden(hidden); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setStopped(boolean stopped) { + if (mUserState != null) { + mUserState.setStopped(stopped); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setNotLaunched(boolean notLaunched) { + if (mUserState != null) { + mUserState.setNotLaunched(notLaunched); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setOverlayPaths(@NonNull OverlayPaths overlayPaths) { + if (mUserState != null) { + mUserState.setOverlayPaths(overlayPaths); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setOverlayPathsForLibrary(@NonNull String libraryName, + @Nullable OverlayPaths overlayPaths) { + if (mUserState != null) { + mUserState.setSharedLibraryOverlayPaths(libraryName, overlayPaths); + } + return this; + } + + @NonNull + @Override + public PackageUserStateWrite setHarmfulAppWarning(@Nullable String warning) { + if (mUserState != null) { + mUserState.setHarmfulAppWarning(warning); + } + return this; + } + } + } +} diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java new file mode 100644 index 000000000000..585becee55c0 --- /dev/null +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java @@ -0,0 +1,41 @@ +/* + * 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.server.pm.pkg.mutate; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.pm.PackageManager; +import android.util.ArraySet; + +public interface PackageStateWrite { + + @NonNull + PackageUserStateWrite userState(@UserIdInt int userId); + + @NonNull + PackageStateWrite setLastPackageUsageTime(@PackageManager.NotifyReason int reason, + long timeInMillis); + + @NonNull + PackageStateWrite setHiddenUntilInstalled(boolean hiddenUntilInstalled); + + @NonNull + PackageStateWrite setRequiredForSystemUser(boolean requiredForSystemUser); + + @NonNull + PackageStateWrite setMimeGroup(@NonNull String mimeGroup, @NonNull ArraySet<String> mimeTypes); +} diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java new file mode 100644 index 000000000000..e23a1b604a49 --- /dev/null +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java @@ -0,0 +1,63 @@ +/* + * 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.server.pm.pkg.mutate; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManager; +import android.content.pm.overlay.OverlayPaths; + +import com.android.server.pm.pkg.SuspendParams; + +public interface PackageUserStateWrite { + + @NonNull + PackageUserStateWrite setInstalled(boolean installed); + + @NonNull + PackageUserStateWrite setUninstallReason(@PackageManager.UninstallReason int reason); + + @NonNull + PackageUserStateWrite setDistractionFlags( + @PackageManager.DistractionRestriction int restrictionFlags); + + @NonNull + PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage, + @Nullable SuspendParams suspendParams); + + @NonNull + PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage); + + @NonNull + PackageUserStateWrite setHidden(boolean hidden); + + @NonNull + PackageUserStateWrite setStopped(boolean stopped); + + @NonNull + PackageUserStateWrite setNotLaunched(boolean notLaunched); + + @NonNull + PackageUserStateWrite setOverlayPaths(@Nullable OverlayPaths overlayPaths); + + @NonNull + PackageUserStateWrite setOverlayPathsForLibrary(@NonNull String libraryName, + @Nullable OverlayPaths overlayPaths); + + @NonNull + PackageUserStateWrite setHarmfulAppWarning(@Nullable String warning); +} diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index a3b0e3e7d02d..d1603f54eb37 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -61,6 +61,7 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.Settings; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; @@ -820,10 +821,11 @@ public class DomainVerificationService extends SystemService PackageStateInternal firstPkgSetting = pkgSettingFunction.apply(first); PackageStateInternal secondPkgSetting = pkgSettingFunction.apply(second); - long firstInstallTime = - firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime(); - long secondInstallTime = - secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime(); + long firstInstallTime = firstPkgSetting == null + ? -1L : firstPkgSetting.getUserStateOrDefault(userId).getFirstInstallTime(); + long secondInstallTime = secondPkgSetting == null + ? -1L + : secondPkgSetting.getUserStateOrDefault(userId).getFirstInstallTime(); if (firstInstallTime != secondInstallTime) { return (int) (firstInstallTime - secondInstallTime); @@ -1650,7 +1652,8 @@ public class DomainVerificationService extends SystemService continue; } - long installTime = pkgSetting.getFirstInstallTime(); + long installTime = PackageStateUtils.getEarliestFirstInstallTime( + pkgSetting.getUserStates()); if (installTime > latestInstall) { latestInstall = installTime; targetPackageName = packageName; @@ -1977,7 +1980,7 @@ public class DomainVerificationService extends SystemService if (pkgSetting == null) { continue; } - long installTime = pkgSetting.getFirstInstallTime(); + long installTime = pkgSetting.getUserStateOrDefault(userId).getFirstInstallTime(); if (installTime > latestInstall) { latestInstall = installTime; filteredPackages.clear(); diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java index 268de3e2182b..68e078c519ba 100644 --- a/services/core/java/com/android/server/policy/KeyCombinationManager.java +++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java @@ -17,11 +17,13 @@ package com.android.server.policy; import static android.view.KeyEvent.KEYCODE_POWER; +import android.os.Handler; import android.os.SystemClock; import android.util.Log; import android.util.SparseLongArray; import android.view.KeyEvent; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ToBooleanFunction; import java.io.PrintWriter; @@ -35,13 +37,18 @@ public class KeyCombinationManager { private static final String TAG = "KeyCombinationManager"; // Store the received down time of keycode. + @GuardedBy("mLock") private final SparseLongArray mDownTimes = new SparseLongArray(2); private final ArrayList<TwoKeysCombinationRule> mRules = new ArrayList(); // Selected rules according to current key down. + private final Object mLock = new Object(); + @GuardedBy("mLock") private final ArrayList<TwoKeysCombinationRule> mActiveRules = new ArrayList(); // The rule has been triggered by current keys. + @GuardedBy("mLock") private TwoKeysCombinationRule mTriggeredRule; + private final Handler mHandler = new Handler(); // Keys in a key combination must be pressed within this interval of each other. private static final long COMBINE_KEY_DELAY_MILLIS = 150; @@ -109,6 +116,12 @@ public class KeyCombinationManager { * Return true if any active rule could be triggered by the key event, otherwise false. */ boolean interceptKey(KeyEvent event, boolean interactive) { + synchronized (mLock) { + return interceptKeyLocked(event, interactive); + } + } + + private boolean interceptKeyLocked(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); @@ -154,7 +167,7 @@ public class KeyCombinationManager { return false; } Log.v(TAG, "Performing combination rule : " + rule); - rule.execute(); + mHandler.post(rule::execute); mTriggeredRule = rule; return true; }); @@ -169,7 +182,7 @@ public class KeyCombinationManager { for (int index = count - 1; index >= 0; index--) { final TwoKeysCombinationRule rule = mActiveRules.get(index); if (rule.shouldInterceptKey(keyCode)) { - rule.cancel(); + mHandler.post(rule::cancel); mActiveRules.remove(index); } } @@ -181,31 +194,37 @@ public class KeyCombinationManager { * Return the interceptTimeout to tell InputDispatcher when is ready to deliver to window. */ long getKeyInterceptTimeout(int keyCode) { - if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) { - return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS; + synchronized (mLock) { + if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) { + return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS; + } + return 0; } - return 0; } /** * True if the key event had been handled. */ boolean isKeyConsumed(KeyEvent event) { - if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { - return false; + synchronized (mLock) { + if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { + return false; + } + return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode()); } - return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode()); } /** * True if power key is the candidate. */ boolean isPowerKeyIntercepted() { - if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) { - // return false if only if power key pressed. - return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0; + synchronized (mLock) { + if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) { + // return false if only if power key pressed. + return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0; + } + return false; } - return false; } /** diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java index a5969a88008d..54ece7384f7a 100644 --- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -138,7 +138,8 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn // By default CLOSE_SYSTEM_DIALOGS broadcast is sent only for current user, which is user // 10 on devices with headless system user enabled. // In order to receive the broadcast, register the broadcast receiver with UserHandle.ALL. - context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); + context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null, + Context.RECEIVER_EXPORTED); mHasTelephony = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY); diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index e857d32c3449..784e177d6362 100644 --- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -112,10 +112,15 @@ class ModifierShortcutManager { * @return The intent that matches the shortcut, or null if not found. */ private Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { + // If a modifier key other than shift is also pressed, skip it. + final boolean isShiftOn = KeyEvent.metaStateHasModifiers(metaState, KeyEvent.META_SHIFT_ON); + if (!isShiftOn && !KeyEvent.metaStateHasNoModifiers(metaState)) { + return null; + } + ShortcutInfo shortcut = null; // If the Shift key is pressed, then search for the shift shortcuts. - boolean isShiftOn = (metaState & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON; SparseArray<ShortcutInfo> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts; // First try the exact keycode (with modifiers). diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 298f102e26cd..28f65cf6d1a0 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -363,6 +363,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { public static final int TOAST_WINDOW_TIMEOUT = 3500 + TOAST_WINDOW_ANIM_BUFFER; /** + * Action for launching assistant in retail mode + */ + private static final String ACTION_VOICE_ASSIST_RETAIL = + "android.intent.action.VOICE_ASSIST_RETAIL"; + + /** * Lock protecting internal state. Must not call out into window * manager with lock held. (This lock will be acquired in places * where the window manager is calling in with its own lock held.) @@ -1090,29 +1096,35 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerManager.boostScreenBrightness(eventTime); break; case MULTI_PRESS_POWER_LAUNCH_TARGET_ACTIVITY: - if (DEBUG_INPUT) { - Slog.d(TAG, "Executing the double press power action."); - } + launchTargetActivityOnMultiPressPower(); + break; + } + } + + private void launchTargetActivityOnMultiPressPower() { + if (DEBUG_INPUT) { + Slog.d(TAG, "Executing the double press power action."); + } + if (mPowerDoublePressTargetActivity != null) { + Intent intent = new Intent(); + intent.setComponent(mPowerDoublePressTargetActivity); + ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( + intent, /* flags= */0); + if (resolveInfo != null) { final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); if (!keyguardActive) { - Intent intent = new Intent(); - if (mPowerDoublePressTargetActivity != null) { - intent.setComponent(mPowerDoublePressTargetActivity); - ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( - intent, /* flags= */0); - if (resolveInfo != null) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); - } else { - Slog.e(TAG, "Could not resolve activity with : " - + mPowerDoublePressTargetActivity.flattenToString() - + " name."); - } - } + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } else { + mKeyguardDelegate.dismissKeyguardToLaunch(intent); } - break; + } else { + Slog.e(TAG, "Could not resolve activity with : " + + mPowerDoublePressTargetActivity.flattenToString() + + " name."); + } } } @@ -1242,6 +1254,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { return LONG_PRESS_POWER_GLOBAL_ACTIONS; } + // If long press to launch assistant is disabled in settings, do nothing. + if (mLongPressOnPowerBehavior == LONG_PRESS_POWER_GO_TO_VOICE_ASSIST + && !isLongPressToAssistantEnabled(mContext)) { + return LONG_PRESS_POWER_NOTHING; + } + return mLongPressOnPowerBehavior; } @@ -1273,6 +1291,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } else { + // If keyguarded then notify the keyguard. + mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY); } break; } @@ -2675,6 +2696,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean canceled = event.isCanceled(); final int displayId = event.getDisplayId(); final long key_consumed = -1; + final long key_not_consumed = 0; if (DEBUG_INPUT) { Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" @@ -2864,7 +2886,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_TAB: if (event.isMetaPressed()) { // Pass through keyboard navigation keys. - return 0; + return key_not_consumed; } // Display task switcher for ALT-TAB. if (down && repeatCount == 0) { @@ -2895,9 +2917,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { return key_consumed; case KeyEvent.KEYCODE_SPACE: - // Handle keyboard layout switching. - if ((metaState & (KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK)) == 0) { - return 0; + // Handle keyboard layout switching. (META + SPACE) + if ((metaState & KeyEvent.META_META_MASK) == 0) { + return key_not_consumed; } // Share the same behavior with KEYCODE_LANGUAGE_SWITCH. case KeyEvent.KEYCODE_LANGUAGE_SWITCH: @@ -2971,7 +2993,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Let the application handle the key. - return 0; + return key_not_consumed; } /** @@ -3037,6 +3059,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { + ", policyFlags=" + policyFlags); } + if (interceptUnhandledKey(event)) { + return null; + } + KeyEvent fallbackEvent = null; if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { final KeyCharacterMap kcm = event.getKeyCharacterMap(); @@ -3091,13 +3117,46 @@ public class PhoneWindowManager implements WindowManagerPolicy { return fallbackEvent; } + private boolean interceptUnhandledKey(KeyEvent event) { + final int keyCode = event.getKeyCode(); + final int repeatCount = event.getRepeatCount(); + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final int metaState = event.getModifiers(); + + switch(keyCode) { + case KeyEvent.KEYCODE_SPACE: + if (down && repeatCount == 0) { + // Handle keyboard layout switching. (CTRL + SPACE) + if (KeyEvent.metaStateHasModifiers(metaState, KeyEvent.META_CTRL_ON)) { + int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; + mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); + return true; + } + } + break; + case KeyEvent.KEYCODE_Z: + if (down && KeyEvent.metaStateHasModifiers(metaState, + KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON)) { + // Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users. + if (mAccessibilityShortcutController + .isAccessibilityShortcutAvailable(isKeyguardLocked())) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT)); + return true; + } + } + break; + } + + return false; + } + private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent, int policyFlags) { int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags); if ((actions & ACTION_PASS_TO_USER) != 0) { long delayMillis = interceptKeyBeforeDispatching( focusedToken, fallbackEvent, policyFlags); - if (delayMillis == 0) { + if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent)) { return true; } } @@ -3218,17 +3277,50 @@ public class PhoneWindowManager implements WindowManagerPolicy { .getSystemService(Context.SEARCH_SERVICE)).launchAssist(args); } - /** Launches ACTION_VOICE_ASSIST. Does nothing on keyguard. */ + /** + * Launches ACTION_VOICE_ASSIST_RETAIL if in retail mode, or ACTION_VOICE_ASSIST otherwise + * Does nothing on keyguard except for watches. Delegates it to keyguard if present on watch. + */ private void launchVoiceAssist(boolean allowDuringSetup) { - final boolean keyguardActive = mKeyguardDelegate == null - ? false - : mKeyguardDelegate.isShowing(); + final boolean keyguardActive = + mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); if (!keyguardActive) { - Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); - startActivityAsUser(intent, null, UserHandle.CURRENT_OR_SELF, + if (mHasFeatureWatch && isInRetailMode()) { + launchRetailVoiceAssist(allowDuringSetup); + } else { + startVoiceAssistIntent(allowDuringSetup); + } + } else { + mKeyguardDelegate.dismissKeyguardToLaunch(new Intent(Intent.ACTION_VOICE_ASSIST)); + } + } + + private void launchRetailVoiceAssist(boolean allowDuringSetup) { + Intent retailIntent = new Intent(ACTION_VOICE_ASSIST_RETAIL); + ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( + retailIntent, /* flags= */0); + if (resolveInfo != null) { + retailIntent.setComponent( + new ComponentName(resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name)); + startActivityAsUser(retailIntent, null, UserHandle.CURRENT_OR_SELF, allowDuringSetup); + } else { + Slog.w(TAG, "Couldn't find an app to process " + ACTION_VOICE_ASSIST_RETAIL + + ". Fall back to start " + Intent.ACTION_VOICE_ASSIST); + startVoiceAssistIntent(allowDuringSetup); } + } + private void startVoiceAssistIntent(boolean allowDuringSetup) { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + startActivityAsUser(intent, null, UserHandle.CURRENT_OR_SELF, + allowDuringSetup); + } + + private boolean isInRetailMode() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DEVICE_DEMO_MODE, 0) == 1; } private void startActivityAsUser(Intent intent, UserHandle handle) { @@ -3935,19 +4027,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users. - if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(isKeyguardLocked())) { - switch (keyCode) { - case KeyEvent.KEYCODE_Z: { - if (down && event.isCtrlPressed() && event.isAltPressed()) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT)); - result &= ~ACTION_PASS_TO_USER; - } - break; - } - } - } - if (useHapticFeedback) { performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false, "Virtual Key - Press"); @@ -5824,6 +5903,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + public static boolean isLongPressToAssistantEnabled(Context context) { + ContentResolver resolver = context.getContentResolver(); + int longPressToAssistant = Settings.System.getIntForUser(resolver, + Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED, + /* def= */ 1, + UserHandle.USER_CURRENT); + if(Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "longPressToAssistant = " + longPressToAssistant); + } + return (longPressToAssistant == 1); + } + private class HdmiVideoExtconUEventObserver extends ExtconStateObserver<Boolean> { private static final String HDMI_EXIST = "HDMI=1"; private static final String NAME = "hdmi"; diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 0080ec6cc989..97a57e066fc7 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -415,6 +415,17 @@ public class KeyguardServiceDelegate { } } + public void dismissKeyguardToLaunch(Intent intentToLaunch) { + if (mKeyguardService != null) { + mKeyguardService.dismissKeyguardToLaunch(intentToLaunch); + } + } + public void onSystemKeyPressed(int keycode) { + if (mKeyguardService != null) { + mKeyguardService.onSystemKeyPressed(keycode); + } + } + public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(SHOWING, mKeyguardState.showing); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 2029f869802e..774e26178db3 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -17,6 +17,7 @@ package com.android.server.policy.keyguard; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.PowerManager; @@ -254,6 +255,24 @@ public class KeyguardServiceWrapper implements IKeyguardService { } } + @Override + public void dismissKeyguardToLaunch(Intent intentToLaunch) { + try { + mService.dismissKeyguardToLaunch(intentToLaunch); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override + public void onSystemKeyPressed(int keycode) { + try { + mService.onSystemKeyPressed(keycode); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + @Override // Binder interface public IBinder asBinder() { return mService.asBinder(); diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index e94575c43363..b03db66fde94 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -213,7 +213,7 @@ public final class ShutdownThread extends Thread { CloseDialogReceiver(Context context) { mContext = context; IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(this, filter); + context.registerReceiver(this, filter, Context.RECEIVER_EXPORTED); } @Override diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java index 7f047f882122..fba0fb49da5c 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ObjectPrinter.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Map; @@ -33,67 +31,28 @@ class ObjectPrinter { static public final int kDefaultMaxCollectionLength = 16; /** - * Simple version of {@link #print(Object, boolean, int)} that prints an object, without - * recursing into sub-objects. - * - * @param obj The object to print. - * @return A string representing the object. - */ - static String print(@Nullable Object obj) { - return print(obj, false, kDefaultMaxCollectionLength); - } - - /** * Pretty-prints an object. * * @param obj The object to print. - * @param deep Whether to pretty-print sub-objects (if false, just prints them - * with {@link Object#toString()}). * @param maxCollectionLength Whenever encountering collections, maximum number of elements to * print. * @return A string representing the object. */ - static String print(@Nullable Object obj, boolean deep, int maxCollectionLength) { + static String print(@Nullable Object obj, int maxCollectionLength) { StringBuilder builder = new StringBuilder(); - print(builder, obj, deep, maxCollectionLength); + print(builder, obj, maxCollectionLength); return builder.toString(); } /** - * This version is suitable for use inside a toString() override of an object, e.g.: - * <pre><code> - * class MyObject { - * ... - * @Override - * String toString() { - * return ObjectPrinter.printPublicFields(this, ...); - * } - * } - * </code></pre> - * - * @param obj The object to print. - * @param deep Whether to pretty-print sub-objects (if false, just prints them - * with {@link Object#toString()}). - * @param maxCollectionLength Whenever encountering collections, maximum number of elements to - * print. - */ - static String printPublicFields(@Nullable Object obj, boolean deep, int maxCollectionLength) { - StringBuilder builder = new StringBuilder(); - printPublicFields(builder, obj, deep, maxCollectionLength); - return builder.toString(); - } - - /** - * A version of {@link #print(Object, boolean, int)} that uses a {@link StringBuilder}. + * A version of {@link #print(Object, int)} that uses a {@link StringBuilder}. * * @param builder StringBuilder to print into. * @param obj The object to print. - * @param deep Whether to pretty-print sub-objects (if false, just prints them - * with {@link Object#toString()}). * @param maxCollectionLength Whenever encountering collections, maximum number of elements to * print. */ - static void print(@NonNull StringBuilder builder, @Nullable Object obj, boolean deep, + static void print(@NonNull StringBuilder builder, @Nullable Object obj, int maxCollectionLength) { try { if (obj == null) { @@ -101,16 +60,16 @@ class ObjectPrinter { return; } if (obj instanceof Boolean) { - builder.append(obj.toString()); + builder.append(obj); return; } if (obj instanceof Number) { - builder.append(obj.toString()); + builder.append(obj); return; } if (obj instanceof Character) { builder.append('\''); - builder.append(obj.toString()); + builder.append(obj); builder.append('\''); return; } @@ -137,7 +96,7 @@ class ObjectPrinter { isLong = true; break; } - print(builder, child, deep, maxCollectionLength); + print(builder, child, maxCollectionLength); ++i; } if (isLong) { @@ -163,9 +122,9 @@ class ObjectPrinter { isLong = true; break; } - print(builder, child.getKey(), deep, maxCollectionLength); + print(builder, child.getKey(), maxCollectionLength); builder.append(": "); - print(builder, child.getValue(), deep, maxCollectionLength); + print(builder, child.getValue(), maxCollectionLength); ++i; } if (isLong) { @@ -189,7 +148,7 @@ class ObjectPrinter { isLong = true; break; } - print(builder, Array.get(obj, i), deep, maxCollectionLength); + print(builder, Array.get(obj, i), maxCollectionLength); } if (isLong) { builder.append("... (+"); @@ -200,48 +159,7 @@ class ObjectPrinter { return; } - if (!deep) { - builder.append(obj.toString()); - return; - } - printPublicFields(builder, obj, deep, maxCollectionLength); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * A version of {@link #printPublicFields(Object, boolean, int)} that uses a {@link - * StringBuilder}. - * - * @param obj The object to print. - * @param deep Whether to pretty-print sub-objects (if false, just prints them - * with {@link Object#toString()}). - * @param maxCollectionLength Whenever encountering collections, maximum number of elements to - * print. - */ - static void printPublicFields(@NonNull StringBuilder builder, @Nullable Object obj, - boolean deep, - int maxCollectionLength) { - try { - Class cls = obj.getClass(); - builder.append("{ "); - - boolean first = true; - for (Field fld : cls.getDeclaredFields()) { - int mod = fld.getModifiers(); - if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.STATIC) == 0) { - if (first) { - first = false; - } else { - builder.append(", "); - } - builder.append(fld.getName()); - builder.append(": "); - print(builder, fld.get(obj), deep, maxCollectionLength); - } - } - builder.append(" }"); + builder.append(obj); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java index 559e777a8c0e..dc4bdaaa8158 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java @@ -18,14 +18,14 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; import android.annotation.Nullable; +import android.media.permission.Identity; +import android.media.permission.IdentityContext; import android.media.soundtrigger.ModelParameterRange; import android.media.soundtrigger.PhraseRecognitionEvent; import android.media.soundtrigger.PhraseSoundModel; import android.media.soundtrigger.RecognitionConfig; import android.media.soundtrigger.RecognitionEvent; import android.media.soundtrigger.SoundModel; -import android.media.permission.Identity; -import android.media.permission.IdentityContext; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; @@ -387,7 +387,7 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt } private static void printObject(@NonNull StringBuilder builder, @Nullable Object obj) { - ObjectPrinter.print(builder, obj, true, 16); + ObjectPrinter.print(builder, obj, 16); } private static String printObject(@Nullable Object obj) { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java index 76927e15a0f7..f3d151fe5cc4 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java @@ -21,9 +21,11 @@ import static android.Manifest.permission.RECORD_AUDIO; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.AppOpsManager; import android.content.Context; import android.content.PermissionChecker; +import android.media.permission.Identity; +import android.media.permission.IdentityContext; +import android.media.permission.PermissionUtil; import android.media.soundtrigger.ModelParameterRange; import android.media.soundtrigger.PhraseRecognitionEvent; import android.media.soundtrigger.PhraseSoundModel; @@ -31,9 +33,6 @@ import android.media.soundtrigger.RecognitionConfig; import android.media.soundtrigger.RecognitionEvent; import android.media.soundtrigger.SoundModel; import android.media.soundtrigger.Status; -import android.media.permission.Identity; -import android.media.permission.IdentityContext; -import android.media.permission.PermissionUtil; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -144,7 +143,7 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware if (status != PermissionChecker.PERMISSION_GRANTED) { throw new SecurityException( String.format("Failed to obtain permission %s for identity %s", permission, - ObjectPrinter.print(identity, true, 16))); + ObjectPrinter.print(identity, 16))); } } @@ -168,7 +167,7 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware case PermissionChecker.PERMISSION_HARD_DENIED: throw new SecurityException( String.format("Failed to obtain permission %s for identity %s", permission, - ObjectPrinter.print(identity, true, 16))); + ObjectPrinter.print(identity, 16))); default: throw new RuntimeException("Unexpected perimission check result."); } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 4243fc775ef6..09035cd1d165 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -18,6 +18,8 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; import android.annotation.Nullable; +import android.media.permission.Identity; +import android.media.permission.IdentityContext; import android.media.soundtrigger.ModelParameterRange; import android.media.soundtrigger.PhraseRecognitionEvent; import android.media.soundtrigger.PhraseSoundModel; @@ -27,8 +29,6 @@ import android.media.soundtrigger.RecognitionEvent; import android.media.soundtrigger.RecognitionStatus; import android.media.soundtrigger.SoundModel; import android.media.soundtrigger.Status; -import android.media.permission.Identity; -import android.media.permission.IdentityContext; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -230,7 +230,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware final ModuleState module = mModules.get(handle); pw.println("========================================="); pw.printf("Module %d\n%s\n", handle, - ObjectPrinter.print(module.properties, true, 16)); + ObjectPrinter.print(module.properties, 16)); pw.println("========================================="); for (Session session : module.sessions) { session.dump(pw); @@ -250,11 +250,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware /** State of a sound model. */ static class ModelState { ModelState(SoundModel model) { - this.description = ObjectPrinter.print(model, true, 16); + this.description = ObjectPrinter.print(model, 16); } ModelState(PhraseSoundModel model) { - this.description = ObjectPrinter.print(model, true, 16); + this.description = ObjectPrinter.print(model, 16); } /** Activity state of a sound model. */ @@ -690,8 +690,8 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware if (mState == ModuleStatus.ALIVE) { pw.println("-------------------------------"); pw.printf("Session %s, client: %s\n", toString(), - ObjectPrinter.print(mOriginatorIdentity, true, 16)); - pw.printf("Loaded models (handle, active, description):", toString()); + ObjectPrinter.print(mOriginatorIdentity, 16)); + pw.println("Loaded models (handle, active, description):"); pw.println(); pw.println("-------------------------------"); for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) { diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 332fed7c1695..568e4b8de3ba 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -44,6 +44,8 @@ import android.graphics.Rect; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.PlaybackParams; +import android.media.tv.AdRequest; +import android.media.tv.AdResponse; import android.media.tv.AitInfo; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; @@ -2494,6 +2496,28 @@ public final class TvInputManagerService extends SystemService { } @Override + public void requestAd(IBinder sessionToken, AdRequest request, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, + userId, "requestAd"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).requestAd(request); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in requestAd", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + }; + } + + @Override public int getClientPid(String sessionId) { ensureTunerResourceAccessPermission(); final long identity = Binder.clearCallingIdentity(); @@ -3557,6 +3581,23 @@ public final class TvInputManagerService extends SystemService { } } } + + @Override + public void onAdResponse (AdResponse response) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onAdResponse()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onAdResponse(response, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onAdResponse", e); + } + } + } } @VisibleForTesting 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 02a56ec8d951..a2bf2fe7df5e 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java @@ -29,6 +29,8 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.graphics.Rect; +import android.media.tv.AdRequest; +import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; @@ -158,6 +160,7 @@ public class TvIAppManagerService extends SystemService { } iAppState.mInfo = info; iAppState.mUid = getIAppUid(info); + iAppState.mComponentName = info.getComponent(); iAppMap.put(iAppServiceId, iAppState); iAppState.mIAppNumber = count; } @@ -1177,6 +1180,28 @@ public class TvIAppManagerService extends SystemService { } @Override + public void notifyAdResponse(IBinder sessionToken, AdResponse response, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyAdResponse"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyAdResponse(response); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyAdResponse", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void registerCallback(final ITvIAppManagerCallback callback, int userId) { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); @@ -1907,6 +1932,23 @@ public class TvIAppManagerService extends SystemService { } @Override + public void onAdRequest(AdRequest request) { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onAdRequest (id=" + request.getId() + ")"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onAdRequest(request, mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onAdRequest", e); + } + } + } + + @Override public void onSessionStateChanged(int state) { synchronized (mLock) { if (DEBUG) { diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java index 39d7a1555dfc..769749038315 100644 --- a/services/core/java/com/android/server/vr/Vr2dDisplay.java +++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java @@ -177,7 +177,7 @@ class Vr2dDisplay { } } } - }, intentFilter); + }, intentFilter, Context.RECEIVER_NOT_EXPORTED); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index fcc2bd6d0032..0b0b70481589 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6337,7 +6337,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } else if (w.isDrawn()) { // The starting window for this container is drawn. - mTaskSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(this); startingDisplayed = true; } } @@ -6906,8 +6905,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getSyncTransaction().hide(mSurfaceControl); } if (show) { - mActivityRecordInputSink.applyChangesToSurfaceIfChanged( - getSyncTransaction(), mSurfaceControl); + mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction()); } } if (mThumbnail != null) { diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java index bce288311e13..9353f6dfdabf 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -60,7 +60,7 @@ class ActivityRecordInputSink { // Hold on to InputEventReceiver to prevent it from getting GCd. private InputEventReceiver mInputEventReceiver; private InputWindowHandleWrapper mInputWindowHandleWrapper; - + private SurfaceControl mSurfaceControl; private int mRapidTouchCount = 0; private IBinder mToken; private boolean mDisabled = false; @@ -73,14 +73,27 @@ class ActivityRecordInputSink { + mActivityRecord.mActivityComponent.getShortClassName(); } - public void applyChangesToSurfaceIfChanged( - SurfaceControl.Transaction transaction, SurfaceControl surfaceControl) { + public void applyChangesToSurfaceIfChanged(SurfaceControl.Transaction transaction) { InputWindowHandleWrapper inputWindowHandleWrapper = getInputWindowHandleWrapper(); + if (mSurfaceControl == null) { + mSurfaceControl = createSurface(transaction); + } if (inputWindowHandleWrapper.isChanged()) { - inputWindowHandleWrapper.applyChangesToSurface(transaction, surfaceControl); + inputWindowHandleWrapper.applyChangesToSurface(transaction, mSurfaceControl); } } + private SurfaceControl createSurface(SurfaceControl.Transaction t) { + SurfaceControl surfaceControl = mActivityRecord.makeChildSurface(null) + .setName(mName) + .setHidden(false) + .setCallsite("ActivityRecordInputSink.createSurface") + .build(); + // Put layer below all siblings (and the parent surface too) + t.setLayer(surfaceControl, Integer.MIN_VALUE); + return surfaceControl; + } + private InputWindowHandleWrapper getInputWindowHandleWrapper() { if (mInputWindowHandleWrapper == null) { mInputWindowHandleWrapper = new InputWindowHandleWrapper(createInputWindowHandle()); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 51c8dafa7c85..3cecce25d195 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -641,22 +641,35 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { intent.setComponent(new ComponentName( aInfo.applicationInfo.packageName, aInfo.name)); - // Don't debug things in the system process - if (!aInfo.processName.equals("system")) { - if ((startFlags & (START_FLAG_DEBUG | START_FLAG_NATIVE_DEBUGGING - | START_FLAG_TRACK_ALLOCATION)) != 0 || profilerInfo != null) { - + final boolean requestDebug = (startFlags & (START_FLAG_DEBUG + | START_FLAG_NATIVE_DEBUGGING | START_FLAG_TRACK_ALLOCATION)) != 0; + final boolean requestProfile = profilerInfo != null; + if (requestDebug || requestProfile) { + final boolean debuggable = (Build.IS_DEBUGGABLE + || (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) + && !aInfo.processName.equals("system"); + if ((requestDebug && !debuggable) || (requestProfile + && (!debuggable && !aInfo.applicationInfo.isProfileableByShell()))) { + Slog.w(TAG, "Ignore debugging for non-debuggable app: " + aInfo.packageName); + } else { // Mimic an AMS synchronous call by passing a message to AMS and wait for AMS // to notify us that the task has completed. // TODO(b/80414790) look into further untangling for the situation where the // caller is on the same thread as the handler we are posting to. synchronized (mService.mGlobalLock) { // Post message to AMS. - final Message msg = PooledLambda.obtainMessage( - ActivityManagerInternal::setDebugFlagsForStartingActivity, - mService.mAmInternal, aInfo, startFlags, profilerInfo, - mService.mGlobalLock); - mService.mH.sendMessage(msg); + mService.mH.post(() -> { + try { + mService.mAmInternal.setDebugFlagsForStartingActivity(aInfo, + startFlags, profilerInfo, mService.mGlobalLock); + } catch (Throwable e) { + // Simply ignore it because the debugging doesn't take effect. + Slog.w(TAG, e); + synchronized (mService.mGlobalLockWithoutBoost) { + mService.mGlobalLockWithoutBoost.notifyAll(); + } + } + }); try { mService.mGlobalLock.wait(); } catch (InterruptedException ignore) { diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 721907c21904..3d37278cd400 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -799,6 +799,22 @@ public class AppTransitionController { } /** + * Returns {@code true} if a given {@link WindowContainer} is an embedded Task. + * + * Note that this is a short term workaround to support Android Auto until it migrate to + * ShellTransition. This should only be used by {@link #getAnimationTargets}. + * + * TODO(b/213312721): Remove this predicate and its callers once ShellTransition is enabled. + */ + private static boolean isEmbeddedTask(WindowContainer wc) { + // We use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and + // it is not guaranteed to work this logic in the future version. + return !WindowManagerService.sEnableShellTransitions + && wc instanceof Task + && ((Task) wc).mRemoveWithTaskOrganizer; + } + + /** * Find WindowContainers to be animated from a set of opening and closing apps. We will promote * animation targets to higher level in the window hierarchy if possible. * @@ -844,7 +860,13 @@ public class AppTransitionController { siblings.add(current); boolean canPromote = true; - if (parent == null || !parent.canCreateRemoteAnimationTarget() + if (isEmbeddedTask(current)) { + // Don't animate an embedded Task in app transition. This is a short term workaround + // to prevent conflict of surface hierarchy changes between legacy app transition + // and TaskView (b/205189147). + // TODO(b/213312721): Remove this once ShellTransition is enabled. + continue; + } else if (parent == null || !parent.canCreateRemoteAnimationTarget() || !parent.canBeAnimationTarget() // We cannot promote the animation on Task's parent when the task is in // clearing task in case the animating get stuck when performing the opening @@ -887,7 +909,13 @@ public class AppTransitionController { for (int j = 0; j < parent.getChildCount(); ++j) { final WindowContainer sibling = parent.getChildAt(j); if (candidates.remove(sibling)) { - siblings.add(sibling); + if (!isEmbeddedTask(sibling)) { + // Don't animate an embedded Task in app transition. This is a short + // term workaround to prevent conflict of surface hierarchy changes + // between legacy app transition and TaskView (b/205189147). + // TODO(b/213312721): Remove this once ShellTransition is enabled. + siblings.add(sibling); + } } else if (sibling != current && sibling.isVisible()) { canPromote = false; } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 3ce6b51900bf..bb4519cfc679 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -59,7 +59,6 @@ import com.android.internal.protolog.common.ProtoLog; */ class BLASTSyncEngine { private static final String TAG = "BLASTSyncEngine"; - private static final String TRACE_NAME_SYNC_GROUP_READY = "SyncGroupReady"; interface TransactionReadyListener { void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); @@ -75,6 +74,7 @@ class BLASTSyncEngine { boolean mReady = false; final ArraySet<WindowContainer> mRootMembers = new ArraySet<>(); private SurfaceControl.Transaction mOrphanTransaction = null; + private String mTraceName; private SyncGroup(TransactionReadyListener listener, int id, String name) { mSyncId = id; @@ -86,8 +86,8 @@ class BLASTSyncEngine { } }; if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { - Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, - name + TRACE_NAME_SYNC_GROUP_READY, id); + mTraceName = name + "SyncGroupReady"; + Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, mTraceName, id); } } @@ -121,7 +121,9 @@ class BLASTSyncEngine { } private void finishNow() { - Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_SYNC_GROUP_READY, mSyncId); + if (mTraceName != null) { + Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, mTraceName, mSyncId); + } ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Finished!", mSyncId); SurfaceControl.Transaction merged = mWm.mTransactionFactory.get(); if (mOrphanTransaction != null) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8ede0160cdce..c740f7b7c2e2 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1467,7 +1467,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override boolean onDescendantOrientationChanged(WindowContainer requestingContainer) { final Configuration config = updateOrientation( - getRequestedOverrideConfiguration(), requestingContainer, false /* forceUpdate */); + requestingContainer, false /* forceUpdate */); // If display rotation class tells us that it doesn't consider app requested orientation, // this display won't rotate just because of an app changes its requested orientation. Thus // it indicates that this display chooses not to handle this request. @@ -1516,14 +1516,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically * be done by calling {@link #sendNewConfiguration}. * - * @param currentConfig The current requested override configuration (it is usually set from - * the last {@link #sendNewConfiguration}) of the display. It is used to - * check if the configuration container has the latest state. * @param freezeDisplayWindow Freeze the app window if the orientation is changed. * @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)} */ - Configuration updateOrientation(Configuration currentConfig, - WindowContainer freezeDisplayWindow, boolean forceUpdate) { + Configuration updateOrientation(WindowContainer<?> freezeDisplayWindow, boolean forceUpdate) { if (!mDisplayReady) { return null; } @@ -1540,15 +1536,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } config = new Configuration(); computeScreenConfiguration(config); - } else if (currentConfig != null + } else if (!(mTransitionController.isCollecting(this) // If waiting for a remote rotation, don't prematurely update configuration. - && !(mDisplayRotation.isWaitingForRemoteRotation() - || mTransitionController.isCollecting(this))) { + || mDisplayRotation.isWaitingForRemoteRotation())) { // No obvious action we need to take, but if our current state mismatches the // activity manager's, update it, disregarding font scale, which should remain set // to the value of the previous configuration. // Here we're calling Configuration#unset() instead of setToDefaults() because we // need to keep override configs clear of non-empty values (e.g. fontSize). + final Configuration currentConfig = getRequestedOverrideConfiguration(); mTmpConfiguration.unset(); mTmpConfiguration.updateFrom(currentConfig); computeScreenConfiguration(mTmpConfiguration); @@ -1598,6 +1594,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ @Rotation int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) { + if (mTransitionController.isShellTransitionsEnabled()) { + return ROTATION_UNDEFINED; + } if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) { return ROTATION_UNDEFINED; } diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java index bbda577e9c57..c85e04dbfa15 100644 --- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java +++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java @@ -59,8 +59,13 @@ public class FadeRotationAnimationController extends FadeAnimationController { /** The list to store the drawn tokens before the rotation animation starts. */ private ArrayList<WindowToken> mPendingShowTokens; - /** It is used when the display has rotated, but some windows fade out in old rotation. */ - private SeamlessRotator mRotator; + /** + * The sync transactions of the target windows. It is used when the display has rotated but + * the windows need to fade out in previous rotation. These transactions will be applied with + * fade-in animation, so there won't be a flickering such as the windows have redrawn during + * fading out. + */ + private ArrayMap<WindowState, SurfaceControl.Transaction> mCapturedDrawTransactions; private final int mOriginalRotation; private final boolean mHasScreenRotationAnimation; @@ -110,16 +115,36 @@ public class FadeRotationAnimationController extends FadeAnimationController { mTargetWindowTokens.put(w.mToken, null); } }, true /* traverseTopToBottom */); + + // The transition sync group may be finished earlier because it doesn't wait for these + // target windows. But the windows still need to use sync transaction to keep the appearance + // in previous rotation, so request a no-op sync to keep the state. + if (!mIsChangeTransition && transitionType != WindowManager.TRANSIT_NONE) { + for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { + final WindowToken token = mTargetWindowTokens.keyAt(i); + for (int j = token.getChildCount() - 1; j >= 0; j--) { + token.getChildAt(j).applyWithNextDraw(t -> {}); + } + } + } } @Override public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) { if (show) { - final SurfaceControl leash = mTargetWindowTokens.remove(windowToken); - if (leash != null && mRotator != null) { - // The leash was unrotated by start transaction of transition. Clear the transform - // to reshow the window in current rotation. - mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), leash); + // The previous animation leash will be dropped when preparing fade-in animation, so + // simply remove it without restoring the transformation. + mTargetWindowTokens.remove(windowToken); + if (mCapturedDrawTransactions != null) { + // Unblock the window to draw its latest content with fade-in animation. + final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction(); + for (int i = windowToken.getChildCount() - 1; i >= 0; i--) { + final SurfaceControl.Transaction drawT = + mCapturedDrawTransactions.remove(windowToken.getChildAt(i)); + if (drawT != null) { + t.merge(drawT); + } + } } } super.fadeWindowToken(show, windowToken, animationType); @@ -225,14 +250,14 @@ public class FadeRotationAnimationController extends FadeAnimationController { // Take OPEN/CLOSE transition type as the example, the non-activity windows need to // fade out in previous rotation while display has rotated to the new rotation, so // their leashes are unrotated with the start transaction. - mRotator = new SeamlessRotator(mOriginalRotation, + final SeamlessRotator rotator = new SeamlessRotator(mOriginalRotation, mDisplayContent.getWindowConfiguration().getRotation(), mDisplayContent.getDisplayInfo(), false /* applyFixedTransformationHint */); for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) { final SurfaceControl leash = mTargetWindowTokens.valueAt(i); if (leash != null) { - mRotator.applyTransform(t, leash); + rotator.applyTransform(t, leash); } } return; @@ -280,6 +305,25 @@ public class FadeRotationAnimationController extends FadeAnimationController { } } + /** Captures the post draw transaction if the window should update with fade-in animation. */ + boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) { + if (mIsChangeTransition || !isTargetToken(w.mToken)) return false; + if (postDrawTransaction != null && w.mTransitionController.inTransition()) { + if (mCapturedDrawTransactions == null) { + mCapturedDrawTransactions = new ArrayMap<>(); + } + final SurfaceControl.Transaction t = mCapturedDrawTransactions.get(w); + if (t == null) { + mCapturedDrawTransactions.put(w, postDrawTransaction); + } else { + t.merge(postDrawTransaction); + } + return true; + } + mDisplayContent.finishFadeRotationAnimation(w.mToken); + return false; + } + @Override public Animation getFadeInAnimation() { if (mHasScreenRotationAnimation) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index dcb28d25ee5d..535bbb74d1e3 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1037,13 +1037,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - final boolean curDisplayInTransitNotAnimate = - // legacy transition - (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) - // shell transition - || (curDisplay.mTransitionController.isShellTransitionsEnabled() - && !curDisplay.mTransitionController.isPlaying()); - if (curDisplayInTransitNotAnimate) { + if (curDisplay.mAppTransition.isRunning() && !curDisplay.isAppTransitioning()) { // We have finished the animation of an app transition. To do this, we have // delayed a lot of operations like showing and hiding apps, moving apps in // Z-order, etc. @@ -1823,8 +1817,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final DisplayContent displayContent = getDisplayContent(displayId); Configuration config = null; if (displayContent != null) { - config = displayContent.updateOrientation( - getDisplayOverrideConfiguration(displayId), starting, true /* forceUpdate */); + config = displayContent.updateOrientation(starting, true /* forceUpdate */); } // Visibilities may change so let the starting activity have a chance to report. Can't do it // when visibility is changed in each AppWindowToken because it may trigger wrong @@ -2561,24 +2554,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mDisplayManagerInternal.setDisplayAccessUIDs(mDisplayAccessUIDs); } - Configuration getDisplayOverrideConfiguration(int displayId) { - final DisplayContent displayContent = getDisplayContentOrCreate(displayId); - if (displayContent == null) { - throw new IllegalArgumentException("No display found with id: " + displayId); - } - - return displayContent.getRequestedOverrideConfiguration(); - } - - void setDisplayOverrideConfiguration(Configuration overrideConfiguration, int displayId) { - final DisplayContent displayContent = getDisplayContentOrCreate(displayId); - if (displayContent == null) { - throw new IllegalArgumentException("No display found with id: " + displayId); - } - - displayContent.onRequestedOverrideConfigurationChanged(overrideConfiguration); - } - void prepareForShutdown() { for (int i = 0; i < getChildCount(); i++) { createSleepToken("shutdown", getChildAt(i).mDisplayId); diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 1533245a75ca..d31b007c56af 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -58,6 +58,7 @@ class RunningTasks { private boolean mAllowed; private boolean mFilterOnlyVisibleRecents; private Task mTopDisplayFocusRootTask; + private Task mTopDisplayAdjacentTask; private RecentTasks mRecentTasks; private boolean mKeepIntentExtra; @@ -81,6 +82,12 @@ class RunningTasks { mRecentTasks = root.mService.getRecentTasks(); mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA; + if (mTopDisplayFocusRootTask.getAdjacentTaskFragment() != null) { + mTopDisplayAdjacentTask = mTopDisplayFocusRootTask.getAdjacentTaskFragment().asTask(); + } else { + mTopDisplayAdjacentTask = null; + } + final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this, PooledLambda.__(Task.class)); root.forAllLeafTasks(c, false); @@ -130,6 +137,12 @@ class RunningTasks { // can be used to determine the order of the tasks (it may not be set for newly // created tasks) task.touchActiveTime(); + } else if (rootTask == mTopDisplayAdjacentTask && rootTask.getTopMostTask() == task) { + // The short-term workaround for launcher could get suitable running task info in + // split screen. + task.touchActiveTime(); + // TreeSet doesn't allow same value and make sure this task is lower than focus one. + task.lastActiveTime--; } mTmpSortedSet.add(task); diff --git a/services/core/java/com/android/server/wm/SplashScreenExceptionList.java b/services/core/java/com/android/server/wm/SplashScreenExceptionList.java index e815a0e2682a..9ca49fe9557e 100644 --- a/services/core/java/com/android/server/wm/SplashScreenExceptionList.java +++ b/services/core/java/com/android/server/wm/SplashScreenExceptionList.java @@ -64,7 +64,8 @@ class SplashScreenExceptionList { mOnPropertiesChangedListener); } - private void updateDeviceConfig(String values) { + @VisibleForTesting + void updateDeviceConfig(String values) { parseDeviceConfigPackageList(values); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index fad87e8875ba..659c7713a613 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4994,8 +4994,7 @@ class Task extends TaskFragment { if (topFragment == f) { return; } - if (!f.isFocusableAndVisible()) { - // No need to resume activity in TaskFragment that is not visible. + if (!f.canBeResumed(null /* starting */)) { return; } resumed[0] |= f.resumeTopActivity(prev, options, deferPause); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 796a90a2cea2..dfb559f93ca3 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -38,7 +38,6 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerService.TAG_ROOT_TASK; import static com.android.server.wm.DisplayContent.alwaysCreateRootTask; -import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -1407,9 +1406,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { leafTask.forAllLeafTaskFragments((taskFrag) -> { final ActivityRecord resumedActivity = taskFrag.getResumedActivity(); - if (resumedActivity != null - && (taskFrag.getVisibility(resuming) != TASK_FRAGMENT_VISIBILITY_VISIBLE - || !taskFrag.isTopActivityFocusable())) { + if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) { if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) { someActivityPaused[0]++; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 97cb512455f6..8299ceae57fd 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1355,6 +1355,17 @@ class TaskFragment extends WindowContainer<WindowContainer> { return getVisibility(starting) != TASK_FRAGMENT_VISIBILITY_INVISIBLE; } + /** + * Returns {@code true} is the activity in this TaskFragment can be resumed. + * + * @param starting The currently starting activity or {@code null} if there is none. + */ + boolean canBeResumed(@Nullable ActivityRecord starting) { + // No need to resume activity in TaskFragment that is not visible. + return isTopActivityFocusable() + && getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE; + } + boolean isFocusableAndVisible() { return isTopActivityFocusable() && shouldBeVisible(null /* starting */); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 1c4004465886..a4771082b3e9 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -497,6 +497,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (fadeRotationController != null) { fadeRotationController.onTransitionFinished(); } + // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget), + // so re-compute in case the IME target is changed after transition. + if (mTransientLaunches != null) { + mTargetDisplay.computeImeTarget(true /* updateImeTarget */); + } } void abort() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index bef7810f5d48..61acb97c9db1 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3354,8 +3354,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< for (int i = mChildren.size() - 1; i >= 0; --i) { mChildren.get(i).finishSync(outMergedTransaction, cancel); } - mSyncState = SYNC_STATE_NONE; if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this); + clearSyncState(); + } + + void clearSyncState() { + mSyncState = SYNC_STATE_NONE; mSyncGroup = null; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index fc1fd92c5ba3..7be128b53f8c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2548,10 +2548,8 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } - if (win.inTransition()) { - focusMayChange = true; - win.mAnimatingExit = true; - } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { + + if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { focusMayChange = true; win.mAnimatingExit = true; } else if (win.mDisplayContent.okToAnimate() && win.isAnimating(TRANSITION | PARENTS, diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index a55f66d72685..573ff2ff9963 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5429,7 +5429,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void assignLayer(Transaction t, int layer) { - if (isStartingWindowAssociatedToTask()) { + if (mStartingData != null) { // The starting window should cover the task. t.setLayer(mSurfaceControl, Integer.MAX_VALUE); return; @@ -5749,29 +5749,36 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms"); mActivityRecord.mRelaunchStartTime = 0; } - - executeDrawHandlers(postDrawTransaction); - - final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null; - mClientWasDrawingForSync = false; - if (!onSyncFinishedDrawing()) { - return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow); - } - - if (mActivityRecord != null - && mTransitionController.isShellTransitionsEnabled() - && mAttrs.type == TYPE_APPLICATION_STARTING) { + if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) { mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger() .notifyStartingWindowDrawn(mActivityRecord); } - if (postDrawTransaction != null) { + final boolean hasSyncHandlers = executeDrawHandlers(postDrawTransaction); + + boolean skipLayout = false; + // Control the timing to switch the appearance of window with different rotations. + final FadeRotationAnimationController fadeRotationController = + mDisplayContent.getFadeRotationAnimationController(); + if (fadeRotationController != null + && fadeRotationController.handleFinishDrawing(this, postDrawTransaction)) { + // Consume the transaction because the controller will apply it with fade animation. + // Layout is not needed because the window will be hidden by the fade leash. Clear + // sync state because its sync transaction doesn't need to be merged to sync group. + postDrawTransaction = null; + skipLayout = true; + clearSyncState(); + } else if (onSyncFinishedDrawing() && postDrawTransaction != null) { mSyncTransaction.merge(postDrawTransaction); + // Consume the transaction because the sync group will merge it. + postDrawTransaction = null; } - mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */); + final boolean layoutNeeded = + mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync); + mClientWasDrawingForSync = false; // We always want to force a traversal after a finish draw for blast sync. - return true; + return !skipLayout && (hasSyncHandlers || layoutNeeded); } void immediatelyNotifyBlastSync() { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 6aa632388ffd..3cd4e5ee82cf 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1595,7 +1595,7 @@ static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong p if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); - message += StringPrintf(" Status=%d", inputChannel.error().code()); + message += StringPrintf(" Status=%d", static_cast<int>(inputChannel.error().code())); jniThrowRuntimeException(env, message.c_str()); return nullptr; } @@ -1629,7 +1629,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong p if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); - message += StringPrintf(" Status=%d", inputChannel.error().code()); + message += StringPrintf(" Status=%d", static_cast<int>(inputChannel.error().code())); jniThrowRuntimeException(env, message.c_str()); return nullptr; } diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 826171a0f10e..be656e3f3a27 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -232,6 +232,11 @@ namespace android { namespace { // Returns true if location has lat/long information. +bool hasLatLong(const GnssLocationAidl& location) { + return (location.gnssLocationFlags & GnssLocationAidl::HAS_LAT_LONG) != 0; +} + +// Returns true if location has lat/long information. bool hasLatLong(const GnssLocation_V1_0& location) { return (static_cast<uint32_t>(location.gnssLocationFlags) & GnssLocationFlags::HAS_LAT_LONG) != 0; @@ -248,6 +253,36 @@ static inline jboolean boolToJbool(bool value) { return value ? JNI_TRUE : JNI_FALSE; } +static GnssLocationAidl createGnssLocation(jint gnssLocationFlags, jdouble latitudeDegrees, + jdouble longitudeDegrees, jdouble altitudeMeters, + jfloat speedMetersPerSec, jfloat bearingDegrees, + jfloat horizontalAccuracyMeters, + jfloat verticalAccuracyMeters, + jfloat speedAccuracyMetersPerSecond, + jfloat bearingAccuracyDegrees, jlong timestamp, + jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos, + jdouble elapsedRealtimeUncertaintyNanos) { + GnssLocationAidl location; + location.gnssLocationFlags = static_cast<int>(gnssLocationFlags); + location.latitudeDegrees = static_cast<double>(latitudeDegrees); + location.longitudeDegrees = static_cast<double>(longitudeDegrees); + location.altitudeMeters = static_cast<double>(altitudeMeters); + location.speedMetersPerSec = static_cast<double>(speedMetersPerSec); + location.bearingDegrees = static_cast<double>(bearingDegrees); + location.horizontalAccuracyMeters = static_cast<double>(horizontalAccuracyMeters); + location.verticalAccuracyMeters = static_cast<double>(verticalAccuracyMeters); + location.speedAccuracyMetersPerSecond = static_cast<double>(speedAccuracyMetersPerSecond); + location.bearingAccuracyDegrees = static_cast<double>(bearingAccuracyDegrees); + location.timestampMillis = static_cast<uint64_t>(timestamp); + + location.elapsedRealtime.flags = static_cast<int>(elapsedRealtimeFlags); + location.elapsedRealtime.timestampNs = static_cast<uint64_t>(elapsedRealtimeNanos); + location.elapsedRealtime.timeUncertaintyNs = + static_cast<double>(elapsedRealtimeUncertaintyNanos); + + return location; +} + static GnssLocation_V1_0 createGnssLocation_V1_0( jint gnssLocationFlags, jdouble latitudeDegrees, jdouble longitudeDegrees, jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees, @@ -298,7 +333,8 @@ struct GnssCallback : public IGnssCallback_V2_1 { Return<void> gnssLocationCb(const GnssLocation_V1_0& location) override; Return<void> gnssStatusCb(const IGnssCallback_V1_0::GnssStatusValue status) override; Return<void> gnssSvStatusCb(const IGnssCallback_V1_0::GnssSvStatus& svStatus) override { - return gnssSvStatusCbImpl(svStatus); + return gnssSvStatusCbImpl<IGnssCallback_V1_0::GnssSvStatus, IGnssCallback_V1_0::GnssSvInfo>( + svStatus); } Return<void> gnssNmeaCb(int64_t timestamp, const android::hardware::hidl_string& nmea) override; Return<void> gnssSetCapabilitesCb(uint32_t capabilities) override; @@ -318,73 +354,67 @@ struct GnssCallback : public IGnssCallback_V2_1 { Return<void> gnssSetCapabilitiesCb_2_0(uint32_t capabilities) override; Return<void> gnssLocationCb_2_0(const GnssLocation_V2_0& location) override; Return<void> gnssSvStatusCb_2_0(const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList) override { - return gnssSvStatusCbImpl(svInfoList); + return gnssSvStatusCbImpl<hidl_vec<IGnssCallback_V2_0::GnssSvInfo>, + IGnssCallback_V1_0::GnssSvInfo>(svInfoList); } // New in 2.1 Return<void> gnssSvStatusCb_2_1(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList) override { - return gnssSvStatusCbImpl(svInfoList); + return gnssSvStatusCbImpl<hidl_vec<IGnssCallback_V2_1::GnssSvInfo>, + IGnssCallback_V1_0::GnssSvInfo>(svInfoList); } Return<void> gnssSetCapabilitiesCb_2_1(uint32_t capabilities) override; // TODO: Reconsider allocation cost vs threadsafety on these statics static const char* sNmeaString; static size_t sNmeaStringLength; -private: - template<class T> - Return<void> gnssLocationCbImpl(const T& location); - template<class T> - Return<void> gnssSvStatusCbImpl(const T& svStatus); + template <class T> + static Return<void> gnssLocationCbImpl(const T& location); - template<class T> - uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) { + template <class T_list, class T_sv_info> + static Return<void> gnssSvStatusCbImpl(const T_list& svStatus); + +private: + template <class T> + static uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) { return 0; } - template<class T> - double getBasebandCn0DbHz(const T& svStatus, size_t i) { + template <class T> + static double getBasebandCn0DbHz(const T& svStatus, size_t i) { return 0.0; } - uint32_t getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) { - return svStatus.numSvs; - } - - uint32_t getGnssSvInfoListSize(const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList) { + template <class T> + static uint32_t getGnssSvInfoListSize(const T& svInfoList) { return svInfoList.size(); } - uint32_t getGnssSvInfoListSize(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList) { - return svInfoList.size(); + static const IGnssCallbackAidl::GnssSvInfo& getGnssSvInfoOfIndex( + const std::vector<IGnssCallbackAidl::GnssSvInfo>& svInfoList, size_t i) { + return svInfoList[i]; } - const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( + static const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( const IGnssCallback_V1_0::GnssSvStatus& svStatus, size_t i) { return svStatus.gnssSvList.data()[i]; } - const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( + static const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList, size_t i) { return svInfoList[i].v1_0; } - const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( + static const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex( const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) { return svInfoList[i].v2_0.v1_0; } - uint32_t getConstellationType(const IGnssCallback_V1_0::GnssSvStatus& svStatus, size_t i) { - return static_cast<uint32_t>(svStatus.gnssSvList.data()[i].constellation); - } - - uint32_t getConstellationType(const hidl_vec<IGnssCallback_V2_0::GnssSvInfo>& svInfoList, size_t i) { + template <class T> + static uint32_t getConstellationType(const T& svInfoList, size_t i) { return static_cast<uint32_t>(svInfoList[i].constellation); } - - uint32_t getConstellationType(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) { - return static_cast<uint32_t>(svInfoList[i].v2_0.constellation); - } }; Return<void> GnssCallback::gnssNameCb(const android::hardware::hidl_string& name) { @@ -441,14 +471,43 @@ uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2 return SVID_FLAGS_HAS_BASEBAND_CN0; } +template <> +uint32_t GnssCallback::getHasBasebandCn0DbHzFlag( + const std::vector<IGnssCallbackAidl::GnssSvInfo>& svStatus) { + return SVID_FLAGS_HAS_BASEBAND_CN0; +} + +template <> +double GnssCallback::getBasebandCn0DbHz( + const std::vector<IGnssCallbackAidl::GnssSvInfo>& svInfoList, size_t i) { + return svInfoList[i].basebandCN0DbHz; +} + template<> double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) { return svInfoList[i].basebandCN0DbHz; } -template<class T> -Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { +template <> +uint32_t GnssCallback::getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) { + return svStatus.numSvs; +} + +template <> +uint32_t GnssCallback::getConstellationType(const IGnssCallback_V1_0::GnssSvStatus& svStatus, + size_t i) { + return static_cast<uint32_t>(svStatus.gnssSvList.data()[i].constellation); +} + +template <> +uint32_t GnssCallback::getConstellationType( + const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) { + return static_cast<uint32_t>(svInfoList[i].v2_0.constellation); +} + +template <class T_list, class T_sv_info> +Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) { JNIEnv* env = getJniEnv(); uint32_t listSize = getGnssSvInfoListSize(svStatus); @@ -476,7 +535,7 @@ Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) { CONSTELLATION_TYPE_SHIFT_WIDTH = 8 }; - const IGnssCallback_V1_0::GnssSvInfo& info = getGnssSvInfoOfIndex(svStatus, i); + const T_sv_info& info = getGnssSvInfoOfIndex(svStatus, i); svidWithFlags[i] = (info.svid << SVID_SHIFT_WIDTH) | (getConstellationType(svStatus, i) << CONSTELLATION_TYPE_SHIFT_WIDTH) | static_cast<uint32_t>(info.svFlag); @@ -586,6 +645,16 @@ Return<void> GnssCallback::gnssSetSystemInfoCb(const IGnssCallback_V2_0::GnssSys class GnssCallbackAidl : public android::hardware::gnss::BnGnssCallback { public: Status gnssSetCapabilitiesCb(const int capabilities) override; + Status gnssStatusCb(const GnssStatusValue status) override; + Status gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) override; + Status gnssLocationCb(const GnssLocationAidl& location) override; + Status gnssNmeaCb(const int64_t timestamp, const std::string& nmea) override; + Status gnssAcquireWakelockCb() override; + Status gnssReleaseWakelockCb() override; + Status gnssSetSystemInfoCb(const GnssSystemInfo& info) override; + Status gnssRequestTimeCb() override; + Status gnssRequestLocationCb(const bool independentFromGnss, + const bool isUserEmergency) override; }; Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) { @@ -596,6 +665,76 @@ Status GnssCallbackAidl::gnssSetCapabilitiesCb(const int capabilities) { return Status::ok(); } +Status GnssCallbackAidl::gnssStatusCb(const GnssStatusValue status) { + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_reportStatus, status); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssSvStatusCb(const std::vector<GnssSvInfo>& svInfoList) { + GnssCallback::gnssSvStatusCbImpl<std::vector<GnssSvInfo>, GnssSvInfo>(svInfoList); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssLocationCb(const GnssLocationAidl& location) { + GnssCallback::gnssLocationCbImpl<GnssLocationAidl>(location); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) { + JNIEnv* env = getJniEnv(); + /* + * The Java code will call back to read these values. + * We do this to avoid creating unnecessary String objects. + */ + GnssCallback::sNmeaString = nmea.c_str(); + GnssCallback::sNmeaStringLength = nmea.size(); + + env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssAcquireWakelockCb() { + acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssReleaseWakelockCb() { + release_wake_lock(WAKE_LOCK_NAME); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssSetSystemInfoCb(const GnssSystemInfo& info) { + ALOGD("%s: yearOfHw=%d, name=%s\n", __func__, info.yearOfHw, info.name.c_str()); + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_setGnssYearOfHardware, info.yearOfHw); + jstring jstringName = env->NewStringUTF(info.name.c_str()); + env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareModelName, jstringName); + if (jstringName) { + env->DeleteLocalRef(jstringName); + } + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssRequestTimeCb() { + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestUtcTime); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); +} + +Status GnssCallbackAidl::gnssRequestLocationCb(const bool independentFromGnss, + const bool isUserEmergency) { + JNIEnv* env = getJniEnv(); + env->CallVoidMethod(mCallbacksObj, method_requestLocation, boolToJbool(independentFromGnss), + boolToJbool(isUserEmergency)); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return Status::ok(); +} + /* * GnssPowerIndicationCallback class implements the callback methods for the IGnssPowerIndication * interface. @@ -797,7 +936,10 @@ Return<void> AGnssRilCallback::requestRefLocCb() { static void android_location_gnss_hal_GnssNative_set_gps_service_handle() { gnssHalAidl = waitForVintfService<IGnssAidl>(); if (gnssHalAidl != nullptr) { - ALOGD("Successfully got GNSS AIDL handle."); + ALOGD("Successfully got GNSS AIDL handle. Version=%d.", gnssHalAidl->getInterfaceVersion()); + if (gnssHalAidl->getInterfaceVersion() >= 2) { + return; + } } ALOGD("Trying IGnss_V2_1::getService()"); @@ -952,15 +1094,17 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject // TODO: linkToDeath for AIDL HAL - gnssHalDeathRecipient = new GnssDeathRecipient(); - hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0); - if (!linked.isOk()) { - ALOGE("Transaction error in linking to GnssHAL death: %s", - linked.description().c_str()); - } else if (!linked) { - ALOGW("Unable to link to GnssHal death notifications"); - } else { - ALOGD("Link to death notification successful"); + if (gnssHal != nullptr) { + gnssHalDeathRecipient = new GnssDeathRecipient(); + hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0); + if (!linked.isOk()) { + ALOGE("Transaction error in linking to GnssHAL death: %s", + linked.description().c_str()); + } else if (!linked) { + ALOGW("Unable to link to GnssHal death notifications"); + } else { + ALOGD("Link to death notification successful"); + } } if (gnssHalAidl != nullptr) { @@ -1163,7 +1307,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssConfigurationIface = std::make_unique<gnss::GnssConfiguration_V1_1>(gnssConfiguration); } - } else { + } else if (gnssHal != nullptr) { auto gnssConfiguration = gnssHal->getExtensionGnssConfiguration(); if (checkHidlReturn(gnssConfiguration, "Unable to get a handle to GnssConfiguration_V1_0")) { @@ -1229,7 +1373,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } static jboolean android_location_gnss_hal_GnssNative_is_supported(JNIEnv* /* env */, jclass) { - return (gnssHal != nullptr) ? JNI_TRUE : JNI_FALSE; + return (gnssHalAidl != nullptr || gnssHal != nullptr) ? JNI_TRUE : JNI_FALSE; } static jboolean android_location_GnssNetworkConnectivityHandler_is_agps_ril_supported( @@ -1263,26 +1407,26 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl return JNI_FALSE; } - Return<bool> result = false; - // Set top level IGnss.hal callback. - sp<IGnssCallback_V2_1> gnssCbIface = new GnssCallback(); - if (gnssHal_V2_1 != nullptr) { - result = gnssHal_V2_1->setCallback_2_1(gnssCbIface); - } else if (gnssHal_V2_0 != nullptr) { - result = gnssHal_V2_0->setCallback_2_0(gnssCbIface); - } else if (gnssHal_V1_1 != nullptr) { - result = gnssHal_V1_1->setCallback_1_1(gnssCbIface); - } else if (gnssHal != nullptr) { - result = gnssHal->setCallback(gnssCbIface); - } - - if (!checkHidlReturn(result, "IGnss setCallback() failed.")) { - return JNI_FALSE; + if (gnssHal != nullptr) { + Return<bool> result = false; + sp<IGnssCallback_V2_1> gnssCbIface = new GnssCallback(); + if (gnssHal_V2_1 != nullptr) { + result = gnssHal_V2_1->setCallback_2_1(gnssCbIface); + } else if (gnssHal_V2_0 != nullptr) { + result = gnssHal_V2_0->setCallback_2_0(gnssCbIface); + } else if (gnssHal_V1_1 != nullptr) { + result = gnssHal_V1_1->setCallback_1_1(gnssCbIface); + } else { + result = gnssHal->setCallback(gnssCbIface); + } + if (!checkHidlReturn(result, "IGnss setCallback() failed.")) { + return JNI_FALSE; + } } - sp<IGnssCallbackAidl> gnssCbIfaceAidl = new GnssCallbackAidl(); if (gnssHalAidl != nullptr) { + sp<IGnssCallbackAidl> gnssCbIfaceAidl = new GnssCallbackAidl(); auto status = gnssHalAidl->setCallback(gnssCbIfaceAidl); if (!checkAidlStatus(status, "IGnssAidl setCallback() failed.")) { return JNI_FALSE; @@ -1298,7 +1442,7 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl } } else if (gnssXtraIface != nullptr) { sp<IGnssXtraCallback> gnssXtraCbIface = new GnssXtraCallback(); - result = gnssXtraIface->setCallback(gnssXtraCbIface); + auto result = gnssXtraIface->setCallback(gnssXtraCbIface); if (!checkHidlReturn(result, "IGnssXtra setCallback() failed.")) { gnssXtraIface = nullptr; } else { @@ -1341,20 +1485,20 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl if (gnssVisibilityControlIface != nullptr) { sp<IGnssVisibilityControlCallback> gnssVisibilityControlCbIface = new GnssVisibilityControlCallback(); - result = gnssVisibilityControlIface->setCallback(gnssVisibilityControlCbIface); + auto result = gnssVisibilityControlIface->setCallback(gnssVisibilityControlCbIface); checkHidlReturn(result, "IGnssVisibilityControl setCallback() failed."); } // Set IMeasurementCorrections.hal callback. if (gnssCorrectionsIface_V1_1 != nullptr) { - sp<IMeasurementCorrectionsCallback> gnssCorrectionsIfaceCbIface = - new MeasurementCorrectionsCallback(); - result = gnssCorrectionsIface_V1_1->setCallback(gnssCorrectionsIfaceCbIface); - checkHidlReturn(result, "IMeasurementCorrections 1.1 setCallback() failed."); + sp<IMeasurementCorrectionsCallback> gnssCorrectionsIfaceCbIface = + new MeasurementCorrectionsCallback(); + auto result = gnssCorrectionsIface_V1_1->setCallback(gnssCorrectionsIfaceCbIface); + checkHidlReturn(result, "IMeasurementCorrections 1.1 setCallback() failed."); } else if (gnssCorrectionsIface_V1_0 != nullptr) { sp<IMeasurementCorrectionsCallback> gnssCorrectionsIfaceCbIface = new MeasurementCorrectionsCallback(); - result = gnssCorrectionsIface_V1_0->setCallback(gnssCorrectionsIfaceCbIface); + auto result = gnssCorrectionsIface_V1_0->setCallback(gnssCorrectionsIfaceCbIface); checkHidlReturn(result, "IMeasurementCorrections 1.0 setCallback() failed."); } else { ALOGI("Unable to find IMeasurementCorrections."); @@ -1388,6 +1532,15 @@ static void android_location_gnss_hal_GnssNative_cleanup(JNIEnv* /* env */, jcla static jboolean android_location_gnss_hal_GnssNative_set_position_mode( JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval, jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->setPositionMode(static_cast<IGnssAidl::GnssPositionMode>(mode), + static_cast<IGnssAidl::GnssPositionRecurrence>( + recurrence), + min_interval, preferred_accuracy, preferred_time, + low_power_mode); + return checkAidlStatus(status, "IGnssAidl setPositionMode() failed."); + } + Return<bool> result = false; if (gnssHal_V1_1 != nullptr) { result = gnssHal_V1_1->setPositionMode_1_1(static_cast<IGnss_V1_0::GnssPositionMode>(mode), @@ -1408,6 +1561,11 @@ static jboolean android_location_gnss_hal_GnssNative_set_position_mode( } static jboolean android_location_gnss_hal_GnssNative_start(JNIEnv* /* env */, jclass) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->start(); + return checkAidlStatus(status, "IGnssAidl start() failed."); + } + if (gnssHal == nullptr) { return JNI_FALSE; } @@ -1417,6 +1575,11 @@ static jboolean android_location_gnss_hal_GnssNative_start(JNIEnv* /* env */, jc } static jboolean android_location_gnss_hal_GnssNative_stop(JNIEnv* /* env */, jclass) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->stop(); + return checkAidlStatus(status, "IGnssAidl stop() failed."); + } + if (gnssHal == nullptr) { return JNI_FALSE; } @@ -1427,6 +1590,12 @@ static jboolean android_location_gnss_hal_GnssNative_stop(JNIEnv* /* env */, jcl static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* env */, jclass, jint flags) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->deleteAidingData(static_cast<IGnssAidl::GnssAidingData>(flags)); + checkAidlStatus(status, "IGnssAidl deleteAidingData() failed."); + return; + } + if (gnssHal == nullptr) { return; } @@ -1490,10 +1659,15 @@ static jint android_location_gnss_hal_GnssNative_read_nmea(JNIEnv* env, jclass, static void android_location_gnss_hal_GnssNative_inject_time(JNIEnv* /* env */, jclass, jlong time, jlong timeReference, jint uncertainty) { - if (gnssHal == nullptr) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->injectTime(time, timeReference, uncertainty); + checkAidlStatus(status, "IGnssAidl injectTime() failed."); return; } + if (gnssHal == nullptr) { + return; + } auto result = gnssHal->injectTime(time, timeReference, uncertainty); checkHidlReturn(result, "IGnss injectTime() failed."); } @@ -1505,6 +1679,19 @@ static void android_location_gnss_hal_GnssNative_inject_best_location( jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees, jlong timestamp, jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos, jdouble elapsedRealtimeUncertaintyNanos) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + GnssLocationAidl location = + createGnssLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees, + altitudeMeters, speedMetersPerSec, bearingDegrees, + horizontalAccuracyMeters, verticalAccuracyMeters, + speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp, + elapsedRealtimeFlags, elapsedRealtimeNanos, + elapsedRealtimeUncertaintyNanos); + auto status = gnssHalAidl->injectBestLocation(location); + checkAidlStatus(status, "IGnssAidl injectBestLocation() failed."); + return; + } + if (gnssHal_V2_0 != nullptr) { GnssLocation_V2_0 location = createGnssLocation_V2_0( gnssLocationFlags, @@ -1546,15 +1733,31 @@ static void android_location_gnss_hal_GnssNative_inject_best_location( ALOGE("IGnss injectBestLocation() is called but gnssHal_V1_1 is not available."); } -static void android_location_gnss_hal_GnssNative_inject_location(JNIEnv* /* env */, jclass, - jdouble latitude, - jdouble longitude, - jfloat accuracy) { - if (gnssHal == nullptr) { +static void android_location_gnss_hal_GnssNative_inject_location( + JNIEnv* /* env */, jclass, jint gnssLocationFlags, jdouble latitudeDegrees, + jdouble longitudeDegrees, jdouble altitudeMeters, jfloat speedMetersPerSec, + jfloat bearingDegrees, jfloat horizontalAccuracyMeters, jfloat verticalAccuracyMeters, + jfloat speedAccuracyMetersPerSecond, jfloat bearingAccuracyDegrees, jlong timestamp, + jint elapsedRealtimeFlags, jlong elapsedRealtimeNanos, + jdouble elapsedRealtimeUncertaintyNanos) { + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + GnssLocationAidl location = + createGnssLocation(gnssLocationFlags, latitudeDegrees, longitudeDegrees, + altitudeMeters, speedMetersPerSec, bearingDegrees, + horizontalAccuracyMeters, verticalAccuracyMeters, + speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp, + elapsedRealtimeFlags, elapsedRealtimeNanos, + elapsedRealtimeUncertaintyNanos); + auto status = gnssHalAidl->injectLocation(location); + checkAidlStatus(status, "IGnssAidl injectLocation() failed."); return; } - auto result = gnssHal->injectLocation(latitude, longitude, accuracy); + if (gnssHal == nullptr) { + return; + } + auto result = + gnssHal->injectLocation(latitudeDegrees, longitudeDegrees, horizontalAccuracyMeters); checkHidlReturn(result, "IGnss injectLocation() failed."); } @@ -1806,15 +2009,20 @@ static jboolean android_location_gnss_hal_GnssNative_is_measurement_supported(JN } static jboolean android_location_gnss_hal_GnssNative_start_measurement_collection( - JNIEnv* /* env */, jclass, jboolean enableFullTracking, jboolean enableCorrVecOutputs) { + JNIEnv* /* env */, jclass, jboolean enableFullTracking, jboolean enableCorrVecOutputs, + jint intervalMs) { if (gnssMeasurementIface == nullptr) { ALOGE("%s: IGnssMeasurement interface not available.", __func__); return JNI_FALSE; } + hardware::gnss::IGnssMeasurementInterface::Options options; + options.enableFullTracking = enableFullTracking; + options.enableCorrVecOutputs = enableCorrVecOutputs; + options.intervalMs = intervalMs; return gnssMeasurementIface->setCallback(std::make_unique<gnss::GnssMeasurementCallback>( mCallbacksObj), - enableFullTracking, enableCorrVecOutputs); + options); } static jboolean android_location_gnss_hal_GnssNative_stop_measurement_collection(JNIEnv* env, @@ -2202,7 +2410,7 @@ static const JNINativeMethod sLocationProviderMethods[] = { reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_time)}, {"native_inject_best_location", "(IDDDFFFFFFJIJD)V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_best_location)}, - {"native_inject_location", "(DDF)V", + {"native_inject_location", "(IDDDFFFFFFJIJD)V", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_location)}, {"native_supports_psds", "()Z", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_supports_psds)}, @@ -2269,7 +2477,7 @@ static const JNINativeMethod sMeasurementMethods[] = { /* name, signature, funcPtr */ {"native_is_measurement_supported", "()Z", reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_is_measurement_supported)}, - {"native_start_measurement_collection", "(ZZ)Z", + {"native_start_measurement_collection", "(ZZI)Z", reinterpret_cast<void*>( android_location_gnss_hal_GnssNative_start_measurement_collection)}, {"native_stop_measurement_collection", "()Z", diff --git a/services/core/jni/gnss/AGnss.cpp b/services/core/jni/gnss/AGnss.cpp index 00403d631306..091fffde6f54 100644 --- a/services/core/jni/gnss/AGnss.cpp +++ b/services/core/jni/gnss/AGnss.cpp @@ -43,7 +43,7 @@ jboolean AGnss::setCallback(const std::unique_ptr<AGnssCallback>& callback) { jboolean AGnss::dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) { ScopedJniString jniApn{env, apn}; - auto status = mIAGnss->dataConnOpen(networkHandle, String16(jniApn.c_str()), + auto status = mIAGnss->dataConnOpen(networkHandle, std::string(jniApn.c_str()), static_cast<IAGnss::ApnIpType>(apnIpType)); return checkAidlStatus(status, "IAGnssAidl dataConnOpen() failed. APN and its IP type not set."); @@ -61,8 +61,8 @@ jboolean AGnss::dataConnFailed() { jboolean AGnss::setServer(JNIEnv* env, jint type, jstring hostname, jint port) { ScopedJniString jniHostName{env, hostname}; - auto status = - mIAGnss->setServer(static_cast<AGnssType>(type), String16(jniHostName.c_str()), port); + auto status = mIAGnss->setServer(static_cast<AGnssType>(type), std::string(jniHostName.c_str()), + port); return checkAidlStatus(status, "IAGnssAidl setServer() failed. Host name and port not set."); } diff --git a/services/core/jni/gnss/GnssMeasurement.cpp b/services/core/jni/gnss/GnssMeasurement.cpp index 663d839ff159..9fbf259d582a 100644 --- a/services/core/jni/gnss/GnssMeasurement.cpp +++ b/services/core/jni/gnss/GnssMeasurement.cpp @@ -50,9 +50,15 @@ GnssMeasurement::GnssMeasurement(const sp<IGnssMeasurementInterface>& iGnssMeasu : mIGnssMeasurement(iGnssMeasurement) {} jboolean GnssMeasurement::setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) { - auto status = mIGnssMeasurement->setCallback(callback->getAidl(), enableFullTracking, - enableCorrVecOutputs); + const IGnssMeasurementInterface::Options& options) { + if (mIGnssMeasurement->getInterfaceVersion() >= 2) { + auto status = mIGnssMeasurement->setCallbackWithOptions(callback->getAidl(), options); + if (checkAidlStatus(status, "IGnssMeasurement setCallbackWithOptions() failed.")) { + return true; + } + } + auto status = mIGnssMeasurement->setCallback(callback->getAidl(), options.enableFullTracking, + options.enableCorrVecOutputs); return checkAidlStatus(status, "IGnssMeasurement setCallback() failed."); } @@ -67,13 +73,16 @@ GnssMeasurement_V1_0::GnssMeasurement_V1_0(const sp<IGnssMeasurement_V1_0>& iGns : mIGnssMeasurement_V1_0(iGnssMeasurement) {} jboolean GnssMeasurement_V1_0::setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) { - if (enableFullTracking == true) { + const IGnssMeasurementInterface::Options& options) { + if (options.enableFullTracking == true) { ALOGW("Full tracking mode is not supported in 1.0 GNSS HAL."); } - if (enableCorrVecOutputs == true) { + if (options.enableCorrVecOutputs == true) { ALOGW("Correlation vector output is not supported in 1.0 GNSS HAL."); } + if (options.intervalMs > 1000) { + ALOGW("Measurement interval is not supported in 1.0 GNSS HAL."); + } auto status = mIGnssMeasurement_V1_0->setCallback(callback->getHidl()); if (!checkHidlReturn(status, "IGnssMeasurement setCallback() failed.")) { return JNI_FALSE; @@ -93,11 +102,15 @@ GnssMeasurement_V1_1::GnssMeasurement_V1_1(const sp<IGnssMeasurement_V1_1>& iGns : GnssMeasurement_V1_0{iGnssMeasurement}, mIGnssMeasurement_V1_1(iGnssMeasurement) {} jboolean GnssMeasurement_V1_1::setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) { - if (enableCorrVecOutputs == true) { + const IGnssMeasurementInterface::Options& options) { + if (options.enableCorrVecOutputs == true) { ALOGW("Correlation vector output is not supported in 1.1 GNSS HAL."); } - auto status = mIGnssMeasurement_V1_1->setCallback_1_1(callback->getHidl(), enableFullTracking); + if (options.intervalMs > 1000) { + ALOGW("Measurement interval is not supported in 1.0 GNSS HAL."); + } + auto status = mIGnssMeasurement_V1_1->setCallback_1_1(callback->getHidl(), + options.enableFullTracking); if (!checkHidlReturn(status, "IGnssMeasurement setCallback_V1_1() failed.")) { return JNI_FALSE; } @@ -111,11 +124,15 @@ GnssMeasurement_V2_0::GnssMeasurement_V2_0(const sp<IGnssMeasurement_V2_0>& iGns : GnssMeasurement_V1_1{iGnssMeasurement}, mIGnssMeasurement_V2_0(iGnssMeasurement) {} jboolean GnssMeasurement_V2_0::setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) { - if (enableCorrVecOutputs == true) { + const IGnssMeasurementInterface::Options& options) { + if (options.enableCorrVecOutputs == true) { ALOGW("Correlation vector output is not supported in 2.0 GNSS HAL."); } - auto status = mIGnssMeasurement_V2_0->setCallback_2_0(callback->getHidl(), enableFullTracking); + if (options.intervalMs > 1000) { + ALOGW("Measurement interval is not supported in 1.0 GNSS HAL."); + } + auto status = mIGnssMeasurement_V2_0->setCallback_2_0(callback->getHidl(), + options.enableFullTracking); if (!checkHidlReturn(status, "IGnssMeasurement setCallback_2_0() failed.")) { return JNI_FALSE; } @@ -129,11 +146,15 @@ GnssMeasurement_V2_1::GnssMeasurement_V2_1(const sp<IGnssMeasurement_V2_1>& iGns : GnssMeasurement_V2_0{iGnssMeasurement}, mIGnssMeasurement_V2_1(iGnssMeasurement) {} jboolean GnssMeasurement_V2_1::setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) { - if (enableCorrVecOutputs == true) { + const IGnssMeasurementInterface::Options& options) { + if (options.enableCorrVecOutputs == true) { ALOGW("Correlation vector output is not supported in 2.1 GNSS HAL."); } - auto status = mIGnssMeasurement_V2_1->setCallback_2_1(callback->getHidl(), enableFullTracking); + if (options.intervalMs > 1000) { + ALOGW("Measurement interval is not supported in 1.0 GNSS HAL."); + } + auto status = mIGnssMeasurement_V2_1->setCallback_2_1(callback->getHidl(), + options.enableFullTracking); if (!checkHidlReturn(status, "IGnssMeasurement setCallback_2_1() failed.")) { return JNI_FALSE; } diff --git a/services/core/jni/gnss/GnssMeasurement.h b/services/core/jni/gnss/GnssMeasurement.h index f0752cd3ab5b..7a95db8ed7b6 100644 --- a/services/core/jni/gnss/GnssMeasurement.h +++ b/services/core/jni/gnss/GnssMeasurement.h @@ -37,16 +37,18 @@ namespace android::gnss { class GnssMeasurementInterface { public: virtual ~GnssMeasurementInterface() {} - virtual jboolean setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) = 0; + virtual jboolean setCallback( + const std::unique_ptr<GnssMeasurementCallback>& callback, + const android::hardware::gnss::IGnssMeasurementInterface::Options& options) = 0; virtual jboolean close() = 0; }; class GnssMeasurement : public GnssMeasurementInterface { public: GnssMeasurement(const sp<android::hardware::gnss::IGnssMeasurementInterface>& iGnssMeasurement); - jboolean setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) override; + jboolean setCallback( + const std::unique_ptr<GnssMeasurementCallback>& callback, + const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override; jboolean close() override; private: @@ -57,8 +59,9 @@ class GnssMeasurement_V1_0 : public GnssMeasurementInterface { public: GnssMeasurement_V1_0( const sp<android::hardware::gnss::V1_0::IGnssMeasurement>& iGnssMeasurement); - jboolean setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) override; + jboolean setCallback( + const std::unique_ptr<GnssMeasurementCallback>& callback, + const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override; jboolean close() override; private: @@ -69,8 +72,9 @@ class GnssMeasurement_V1_1 : public GnssMeasurement_V1_0 { public: GnssMeasurement_V1_1( const sp<android::hardware::gnss::V1_1::IGnssMeasurement>& iGnssMeasurement); - jboolean setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) override; + jboolean setCallback( + const std::unique_ptr<GnssMeasurementCallback>& callback, + const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override; private: const sp<android::hardware::gnss::V1_1::IGnssMeasurement> mIGnssMeasurement_V1_1; @@ -80,8 +84,9 @@ class GnssMeasurement_V2_0 : public GnssMeasurement_V1_1 { public: GnssMeasurement_V2_0( const sp<android::hardware::gnss::V2_0::IGnssMeasurement>& iGnssMeasurement); - jboolean setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) override; + jboolean setCallback( + const std::unique_ptr<GnssMeasurementCallback>& callback, + const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override; private: const sp<android::hardware::gnss::V2_0::IGnssMeasurement> mIGnssMeasurement_V2_0; @@ -91,8 +96,9 @@ class GnssMeasurement_V2_1 : public GnssMeasurement_V2_0 { public: GnssMeasurement_V2_1( const sp<android::hardware::gnss::V2_1::IGnssMeasurement>& iGnssMeasurement); - jboolean setCallback(const std::unique_ptr<GnssMeasurementCallback>& callback, - bool enableFullTracking, bool enableCorrVecOutputs) override; + jboolean setCallback( + const std::unique_ptr<GnssMeasurementCallback>& callback, + const android::hardware::gnss::IGnssMeasurementInterface::Options& options) override; private: const sp<android::hardware::gnss::V2_1::IGnssMeasurement> mIGnssMeasurement_V2_1; diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 4b12fd41f747..8203c1b731c4 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -354,6 +354,7 @@ class PackageManagerComponentLabelIconOverrideTest { PackageManager.PERMISSION_GRANTED } } + val mockSharedLibrariesImpl: SharedLibrariesImpl = mock() val mockInjector: PackageManagerServiceInjector = mock { whenever(this.lock) { PackageManagerTracedLock() } whenever(this.componentResolver) { mockComponentResolver } @@ -366,6 +367,7 @@ class PackageManagerComponentLabelIconOverrideTest { whenever(this.appsFilter) { mockAppsFilter } whenever(this.context) { mockContext } whenever(this.getHandler()) { testHandler } + whenever(this.sharedLibrariesImpl) { mockSharedLibrariesImpl } } val testParams = PackageManagerServiceTestParams().apply { this.pendingPackageBroadcasts = mockPendingBroadcasts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index 38e882e252be..334f503a4bfb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -301,6 +301,7 @@ class DomainVerificationManagerApiTest { whenever(isInstalled) { true } whenever(isSuspended) { false } whenever(isInstantApp) { false } + whenever(firstInstallTime) {0L} } }) val pkg2 = mockPkgState(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2)) @@ -548,7 +549,6 @@ class DomainVerificationManagerApiTest { whenever(getPkg()) { pkg } whenever(packageName) { pkgName } whenever(this.domainSetId) { domainSetId } - whenever(firstInstallTime) { 0L } whenever(getUserStateOrDefault(0)) { pkgUserState0() } whenever(getUserStateOrDefault(1)) { pkgUserState1() } whenever(userStates) { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index ad79c65691c3..fb581d70a5fc 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -896,7 +896,6 @@ class DomainVerificationPackageTest { whenever(this.pkg) { pkg } whenever(packageName) { pkgName } whenever(this.domainSetId) { domainSetId } - whenever(firstInstallTime) { 0L } whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT } whenever(getUserStateOrDefault(10)) { PackageUserStateInternal.DEFAULT } whenever(userStates) { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 9fa1a2353f71..728da4992893 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -147,7 +147,6 @@ class DomainVerificationUserStateOverrideTest { whenever(this.pkg) { pkg } whenever(packageName) { pkgName } whenever(this.domainSetId) { domainSetId } - whenever(firstInstallTime) { 0L } whenever(getUserStateOrDefault(0)) { PackageUserStateInternal.DEFAULT } whenever(getUserStateOrDefault(1)) { PackageUserStateInternal.DEFAULT } whenever(userStates) { diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml index 48cee7effa6e..eb741cad4ad9 100644 --- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml +++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml @@ -18,7 +18,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="test_com.android.server"> <!-- APEX does not have classes.dex --> - <application android:hasCode="false"> + <application android:hasCode="false" android:testOnly="true"> <apex-system-service android:name="com.android.server.testing.FakeApexSystemService" android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar" diff --git a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java index d6db1b2f2766..7ebf014549b9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java @@ -29,6 +29,7 @@ import static com.android.server.wm.ActivityInterceptorCallback.COMMUNAL_MODE_OR import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; @@ -138,7 +139,7 @@ public class CommunalManagerServiceTest { ArgumentCaptor<BroadcastReceiver> packageReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mContextSpy).registerReceiverAsUser(packageReceiverCaptor.capture(), - eq(UserHandle.SYSTEM), any(), any(), any()); + eq(UserHandle.SYSTEM), any(), any(), any(), anyInt()); mPackageReceiver = packageReceiverCaptor.getValue(); mBinder = mService.getBinderServiceInstance(); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java index e0c8b09aae88..93a2d317c40e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java @@ -234,6 +234,7 @@ public final class FakeGnssHal extends GnssNative.GnssHal { private boolean mMeasurementCollectionStarted = false; private boolean mMeasurementCollectionFullTracking = false; private boolean mMeasurementCollectionCorrVecOutputsEnabled = false; + private int mMeasurementCollectionIntervalMillis = 0; private GnssHalPositionMode mPositionMode = new GnssHalPositionMode(); private GnssHalBatchingMode mBatchingMode = new GnssHalBatchingMode(); private final ArrayList<Location> mBatchedLocations = new ArrayList<>(); @@ -428,11 +429,15 @@ public final class FakeGnssHal extends GnssNative.GnssHal { } @Override - protected void injectLocation(double latitude, double longitude, float accuracy) { + protected void injectLocation(@GnssLocationFlags int gnssLocationFlags, double latitude, + double longitude, double altitude, float speed, float bearing, float horizontalAccuracy, + float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp, + @GnssRealtimeFlags int elapsedRealtimeFlags, long elapsedRealtimeNanos, + double elapsedRealtimeUncertaintyNanos) { mState.mInjectedLocation = new Location("injected"); mState.mInjectedLocation.setLatitude(latitude); mState.mInjectedLocation.setLongitude(longitude); - mState.mInjectedLocation.setAccuracy(accuracy); + mState.mInjectedLocation.setAccuracy(horizontalAccuracy); } @Override @@ -523,10 +528,11 @@ public final class FakeGnssHal extends GnssNative.GnssHal { @Override protected boolean startMeasurementCollection(boolean enableFullTracking, - boolean enableCorrVecOutputs) { + boolean enableCorrVecOutputs, int intervalMillis) { mState.mMeasurementCollectionStarted = true; mState.mMeasurementCollectionFullTracking = enableFullTracking; mState.mMeasurementCollectionCorrVecOutputsEnabled = enableCorrVecOutputs; + mState.mMeasurementCollectionIntervalMillis = intervalMillis; return true; } @@ -535,6 +541,7 @@ public final class FakeGnssHal extends GnssNative.GnssHal { mState.mMeasurementCollectionStarted = false; mState.mMeasurementCollectionFullTracking = false; mState.mMeasurementCollectionCorrVecOutputsEnabled = false; + mState.mMeasurementCollectionIntervalMillis = 0; return true; } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index ae5984a41770..44a8b3001a71 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -121,6 +121,9 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { /** The active map simulating the in memory storage of Settings */ private val mSettingsMap = WatchedArrayMap<String, PackageSetting>() + /** The shared libraries on the device */ + private lateinit var mSharedLibraries: SharedLibrariesImpl + init { PropertyInvalidatedCache.disableForTestMode() val apply = ExtendedMockito.mockitoSession() @@ -324,6 +327,11 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { any(AndroidPackage::class.java), anyBoolean(), any(File::class.java))) { PackageAbiHelper.NativeLibraryPaths("", false, "", "") } + whenever(mocks.injector.bootstrap(any(PackageManagerService::class.java))) { + mSharedLibraries = SharedLibrariesImpl( + getArgument<Any>(0) as PackageManagerService, mocks.injector) + } + whenever(mocks.injector.sharedLibrariesImpl) { mSharedLibraries } // everything visible by default whenever(mocks.appsFilter.shouldFilterApplication( anyInt(), nullable(), nullable(), anyInt())) { false } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index 663bb2bd2d67..f2415b4665d5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -41,7 +41,7 @@ import android.content.pm.ApexStagedEvent; import android.content.pm.IStagedApexObserver; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; -import android.content.pm.PackageInstaller.SessionInfo.StagedSessionErrorCode; +import android.content.pm.PackageInstaller.SessionInfo.SessionErrorCode; import android.content.pm.StagedApexInfo; import android.os.SystemProperties; import android.os.storage.IStorageManager; @@ -774,7 +774,7 @@ public class StagingManagerTest { private boolean mIsReady = false; private boolean mIsApplied = false; private boolean mIsFailed = false; - private @StagedSessionErrorCode int mErrorCode = -1; + private @SessionErrorCode int mErrorCode = -1; private String mErrorMessage; private boolean mIsDestroyed = false; private int mParentSessionId = -1; @@ -827,7 +827,7 @@ public class StagingManagerTest { return this; } - private @StagedSessionErrorCode int getErrorCode() { + private @SessionErrorCode int getErrorCode() { return mErrorCode; } @@ -939,7 +939,7 @@ public class StagingManagerTest { } @Override - public void setSessionFailed(@StagedSessionErrorCode int errorCode, String errorMessage) { + public void setSessionFailed(@SessionErrorCode int errorCode, String errorMessage) { Preconditions.checkState(!mIsApplied, "Already marked as applied"); mIsFailed = true; mErrorCode = errorCode; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index e93e5444870e..82b75408ad18 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -247,4 +247,21 @@ public class AccessibilityServiceConnectionTest { verify(mMockServiceClient).onPerformGestureResult(0, false); } + @Test + public void unbind_resetAllMagnification() { + mConnection.unbindLocked(); + verify(mMockMagnificationProcessor).resetAllIfNeeded(anyInt()); + } + + @Test + public void binderDied_resetAllMagnification() { + setServiceBinding(COMPONENT_NAME); + mConnection.bindLocked(); + mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder); + + mConnection.binderDied(); + + verify(mMockMagnificationProcessor).resetAllIfNeeded(anyInt()); + } + } 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 621507e2bfc8..99d6c2af6f75 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @@ -235,7 +236,17 @@ public class MagnificationProcessorTest { mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false); - verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY); + verify(mMockWindowMagnificationManager).disableWindowMagnification(TEST_DISPLAY, false, + null); + } + + @Test + public void resetAllIfNeeded_resetFullscreenAndWindowMagnificationByConnectionId() { + final int connectionId = 1; + mMagnificationProcessor.resetAllIfNeeded(connectionId); + + verify(mMockFullScreenMagnificationController).resetAllIfNeeded(eq(connectionId)); + verify(mMockWindowMagnificationManager).resetAllIfNeeded(eq(connectionId)); } @Test @@ -322,7 +333,7 @@ public class MagnificationProcessorTest { final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig( TEST_DISPLAY); verify(mMockMagnificationController).transitionMagnificationConfigMode(eq(TEST_DISPLAY), - eq(newConfig), anyBoolean()); + eq(newConfig), anyBoolean(), anyInt()); assertConfigEquals(newConfig, result); } @@ -438,7 +449,7 @@ public class MagnificationProcessorTest { anyFloat(), anyFloat(), anyFloat()); doAnswer(enableWindowMagnificationStubAnswer).when( mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY), - anyFloat(), anyFloat(), anyFloat(), any()); + anyFloat(), anyFloat(), anyFloat(), any(), anyInt()); } public void resetAndStubMethods() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 08421de3183a..0054fc328932 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -125,6 +125,7 @@ public class MagnificationControllerTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); FakeSettingsProvider.clearSettingsProvider(); + final Object globalLock = new Object(); LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal); @@ -139,14 +140,14 @@ public class MagnificationControllerTest { CURRENT_USER_ID); mScaleProvider = spy(new MagnificationScaleProvider(mContext)); mWindowMagnificationManager = Mockito.spy( - new WindowMagnificationManager(mContext, CURRENT_USER_ID, + new WindowMagnificationManager(mContext, globalLock, mock(WindowMagnificationManager.Callback.class), mTraceManager, mScaleProvider)); mMockConnection = new MockWindowMagnificationConnection(true); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mScreenMagnificationControllerStubber = new FullScreenMagnificationControllerStubber( mScreenMagnificationController); - mMagnificationController = new MagnificationController(mService, new Object(), mContext, + mMagnificationController = new MagnificationController(mService, globalLock, mContext, mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider); mMagnificationController.setMagnificationCapabilities( @@ -293,7 +294,7 @@ public class MagnificationControllerTest { // Enable window magnification while animating. mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, - Float.NaN, Float.NaN, null); + Float.NaN, Float.NaN, null, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); @@ -310,7 +311,7 @@ public class MagnificationControllerTest { mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, obtainMagnificationConfig(MODE_WINDOW), - false); + false, TEST_SERVICE_ID); verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false)); mMockConnection.invokeCallbacks(); @@ -325,13 +326,13 @@ public class MagnificationControllerTest { activateMagnifier(MODE_WINDOW, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, obtainMagnificationConfig(MODE_FULLSCREEN), - animate); + animate, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, - animate, MAGNIFICATION_GESTURE_HANDLER_ID); + animate, TEST_SERVICE_ID); } @Test @@ -345,7 +346,7 @@ public class MagnificationControllerTest { // Config-setting mode mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, obtainMagnificationConfig(MODE_FULLSCREEN), - true); + true, TEST_SERVICE_ID); assertEquals(DEFAULT_SCALE, mScreenMagnificationController.getScale(TEST_DISPLAY), 0); assertEquals(MAGNIFIED_CENTER_X, mScreenMagnificationController.getCenterX(TEST_DISPLAY), @@ -365,7 +366,7 @@ public class MagnificationControllerTest { // Config-setting mode mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, obtainMagnificationConfig(MODE_FULLSCREEN), - true); + true, TEST_SERVICE_ID); verify(mTransitionCallBack, never()).onResult(TEST_DISPLAY, true); } @@ -772,7 +773,7 @@ public class MagnificationControllerTest { centerY, true, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); } else { mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, DEFAULT_SCALE, - centerX, centerY, null); + centerX, centerY, null, TEST_SERVICE_ID); mMockConnection.invokeCallbacks(); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java index 0659a6019336..4c03ec34f074 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java @@ -35,6 +35,9 @@ import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.accessibility.IWindowMagnificationConnection; import android.view.accessibility.IWindowMagnificationConnectionCallback; +import java.util.ArrayList; +import java.util.List; + /** * Mocks the basic logic of window magnification in System UI. We assume the screen size is * unlimited, so source bounds is always on the center of the mirror window bounds. @@ -42,6 +45,8 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback; class MockWindowMagnificationConnection { public static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; + public static final int TEST_DISPLAY_2 = Display.DEFAULT_DISPLAY + 1; + private final List mValidDisplayIds; private final IWindowMagnificationConnection mConnection; private final Binder mBinder; private final boolean mSuspendCallback; @@ -60,6 +65,10 @@ class MockWindowMagnificationConnection { } MockWindowMagnificationConnection(boolean suspendCallback) throws RemoteException { + mValidDisplayIds = new ArrayList(); + mValidDisplayIds.add(TEST_DISPLAY); + mValidDisplayIds.add(TEST_DISPLAY_2); + mSuspendCallback = suspendCallback; mConnection = mock(IWindowMagnificationConnection.class); mBinder = mock(Binder.class); @@ -86,8 +95,8 @@ class MockWindowMagnificationConnection { private void stubEnableWindowMagnification() throws RemoteException { doAnswer((invocation) -> { final int displayId = invocation.getArgument(0); - if (displayId != TEST_DISPLAY) { - throw new IllegalArgumentException("only support default display :" + displayId); + if (!mValidDisplayIds.contains(displayId)) { + throw new IllegalArgumentException("Not support display :" + displayId); } mWindowMagnificationEnabled = true; final float scale = invocation.getArgument(1); @@ -107,8 +116,8 @@ class MockWindowMagnificationConnection { private void stubDisableWindowMagnification() throws RemoteException { doAnswer((invocation) -> { final int displayId = invocation.getArgument(0); - if (displayId != TEST_DISPLAY) { - throw new IllegalArgumentException("only support default display :" + displayId); + if (!mValidDisplayIds.contains(displayId)) { + throw new IllegalArgumentException("Not support display :" + displayId); } setAnimationCallback(invocation.getArgument(1)); mHasPendingCallback = true; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index b807c11d5a5c..e9f0bd9db9fe 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -85,7 +85,7 @@ public class WindowMagnificationGestureHandlerTest { @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0, + mWindowMagnificationManager = new WindowMagnificationManager(mContext, new Object(), mock(WindowMagnificationManager.Callback.class), mMockTrace, new MagnificationScaleProvider(mContext)); mMockConnection = new MockWindowMagnificationConnection(); 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 85512f36da41..a62c0d5e1eaf 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 @@ -16,6 +16,9 @@ package com.android.server.accessibility.magnification; +import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY; +import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY_2; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; @@ -38,13 +41,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.Region; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.test.mock.MockContentResolver; -import android.view.Display; import android.view.InputDevice; import android.view.MotionEvent; import android.view.accessibility.IRemoteMagnificationAnimationCallback; @@ -67,8 +70,8 @@ import org.mockito.invocation.InvocationOnMock; */ public class WindowMagnificationManagerTest { - private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM; + private static final int SERVICE_ID = 1; private MockWindowMagnificationConnection mMockConnection; @Mock @@ -91,7 +94,7 @@ public class WindowMagnificationManagerTest { LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal); mResolver = new MockContentResolver(); mMockConnection = new MockWindowMagnificationConnection(); - mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID, + mWindowMagnificationManager = new WindowMagnificationManager(mContext, new Object(), mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext)); when(mContext.getContentResolver()).thenReturn(mResolver); @@ -185,7 +188,7 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f, - mAnimationCallback); + mAnimationCallback, SERVICE_ID); verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f), eq(200f), eq(300f), eq(0f), eq(0f), @@ -377,14 +380,51 @@ public class WindowMagnificationManagerTest { } @Test - public void resetMagnification_enabled_windowMagnifierDisabled() { + public void requestConnectionToNull_expectedGetterResults() { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 1, 1); + + mWindowMagnificationManager.requestConnection(false); + + assertEquals(1f, mWindowMagnificationManager.getScale(TEST_DISPLAY), 0); + assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterX(TEST_DISPLAY))); + assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterY(TEST_DISPLAY))); + final Region bounds = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds); + assertTrue(bounds.isEmpty()); + } + + @Test + public void resetAllMagnification_enabledBySameId_windowMagnifiersDisabled() { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY_2, 3f, + 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); - mWindowMagnificationManager.reset(TEST_DISPLAY); + mWindowMagnificationManager.resetAllIfNeeded(SERVICE_ID); assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); + } + + @Test + public void resetAllMagnification_enabledByDifferentId_windowMagnifierDisabled() { + final int serviceId2 = SERVICE_ID + 1; + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, SERVICE_ID); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY_2, 3f, + 100f, 200f, null, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER, serviceId2); + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); + + mWindowMagnificationManager.resetAllIfNeeded(SERVICE_ID); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY_2)); } @Test @@ -439,6 +479,22 @@ public class WindowMagnificationManagerTest { } @Test + public void magnifierGetters_disabled_expectedValues() { + mWindowMagnificationManager.requestConnection(true); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); + + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); + + assertEquals(1f, mWindowMagnificationManager.getScale(TEST_DISPLAY), 0); + assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterX(TEST_DISPLAY))); + assertTrue(Float.isNaN(mWindowMagnificationManager.getCenterY(TEST_DISPLAY))); + final Region bounds = new Region(); + mWindowMagnificationManager.getMagnificationSourceBounds(TEST_DISPLAY, bounds); + assertTrue(bounds.isEmpty()); + } + + @Test public void onDisplayRemoved_enabledOnTestDisplay_disabled() { mWindowMagnificationManager.requestConnection(true); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); diff --git a/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java index d5a28f6b1d76..d2ea9c4056e6 100644 --- a/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java @@ -15,7 +15,7 @@ */ package com.android.server.autofill; -import static com.android.server.autofill.AutofillManagerService.getWhitelistedCompatModePackages; +import static com.android.server.autofill.AutofillManagerService.getAllowedCompatModePackages; import static com.google.common.truth.Truth.assertThat; @@ -29,54 +29,54 @@ import java.util.Map; public class AutofillManagerServiceTest { @Test - public void testGetWhitelistedCompatModePackages_null() { - assertThat(getWhitelistedCompatModePackages(null)).isNull(); + public void testGetAllowedCompatModePackages_null() { + assertThat(getAllowedCompatModePackages(null)).isNull(); } @Test - public void testGetWhitelistedCompatModePackages_empty() { - assertThat(getWhitelistedCompatModePackages("")).isNull(); + public void testGetAllowedCompatModePackages_empty() { + assertThat(getAllowedCompatModePackages("")).isNull(); } @Test - public void testGetWhitelistedCompatModePackages_onePackageNoUrls() { - assertThat(getWhitelistedCompatModePackages("one_is_the_loniest_package")) + public void testGetAllowedCompatModePackages_onePackageNoUrls() { + assertThat(getAllowedCompatModePackages("one_is_the_loniest_package")) .containsExactly("one_is_the_loniest_package", null); } @Test - public void testGetWhitelistedCompatModePackages_onePackageMissingEndDelimiter() { - assertThat(getWhitelistedCompatModePackages("one_is_the_loniest_package[")).isEmpty(); + public void testGetAllowedCompatModePackages_onePackageMissingEndDelimiter() { + assertThat(getAllowedCompatModePackages("one_is_the_loniest_package[")).isEmpty(); } @Test - public void testGetWhitelistedCompatModePackages_onePackageOneUrl() { + public void testGetAllowedCompatModePackages_onePackageOneUrl() { final Map<String, String[]> result = - getWhitelistedCompatModePackages("one_is_the_loniest_package[url]"); + getAllowedCompatModePackages("one_is_the_loniest_package[url]"); assertThat(result).hasSize(1); assertThat(result.get("one_is_the_loniest_package")).asList().containsExactly("url"); } @Test - public void testGetWhitelistedCompatModePackages_onePackageMultipleUrls() { + public void testGetAllowedCompatModePackages_onePackageMultipleUrls() { final Map<String, String[]> result = - getWhitelistedCompatModePackages("one_is_the_loniest_package[4,5,8,15,16,23,42]"); + getAllowedCompatModePackages("one_is_the_loniest_package[4,5,8,15,16,23,42]"); assertThat(result).hasSize(1); assertThat(result.get("one_is_the_loniest_package")).asList() .containsExactly("4", "5", "8", "15", "16", "23", "42"); } @Test - public void testGetWhitelistedCompatModePackages_multiplePackagesOneInvalid() { - final Map<String, String[]> result = getWhitelistedCompatModePackages("one:two["); + public void testGetAllowedCompatModePackages_multiplePackagesOneInvalid() { + final Map<String, String[]> result = getAllowedCompatModePackages("one:two["); assertThat(result).hasSize(1); assertThat(result.get("one")).isNull(); } @Test - public void testGetWhitelistedCompatModePackages_multiplePackagesMultipleUrls() { + public void testGetAllowedCompatModePackages_multiplePackagesMultipleUrls() { final Map<String, String[]> result = - getWhitelistedCompatModePackages("p1[p1u1]:p2:p3[p3u1,p3u2]"); + getAllowedCompatModePackages("p1[p1u1]:p2:p3[p3u1,p3u2]"); assertThat(result).hasSize(3); assertThat(result.get("p1")).asList().containsExactly("p1u1"); assertThat(result.get("p2")).isNull(); @@ -84,9 +84,9 @@ public class AutofillManagerServiceTest { } @Test - public void testGetWhitelistedCompatModePackages_threePackagesOneInvalid() { + public void testGetAllowedCompatModePackages_threePackagesOneInvalid() { final Map<String, String[]> result = - getWhitelistedCompatModePackages("p1[p1u1]:p2[:p3[p3u1,p3u2]"); + getAllowedCompatModePackages("p1[p1u1]:p2[:p3[p3u1,p3u2]"); assertThat(result).hasSize(2); assertThat(result.get("p1")).asList().containsExactly("p1u1"); assertThat(result.get("p3")).asList().containsExactly("p3u1", "p3u2"); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index eda05bf3f214..b811e28a3f71 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -129,6 +129,7 @@ import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; +import android.net.wifi.WifiInfo; import android.os.Binder; import android.os.Handler; import android.os.INetworkManagementService; @@ -226,13 +227,13 @@ public class NetworkPolicyManagerServiceTest { private static final long TEST_START = 1194220800000L; private static final String TEST_IFACE = "test0"; - private static final String TEST_SSID = "AndroidAP"; + private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey"; private static final String TEST_IMSI = "310210"; private static final int TEST_SUB_ID = 42; private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS); - private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID); + private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY); private static NetworkTemplate sTemplateCarrierMetered = buildTemplateCarrierMetered(TEST_IMSI); @@ -2096,10 +2097,13 @@ public class NetworkPolicyManagerServiceTest { } private static NetworkStateSnapshot buildWifi() { + WifiInfo mockWifiInfo = mock(WifiInfo.class); + when(mockWifiInfo.makeCopy(anyLong())).thenReturn(mockWifiInfo); + when(mockWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_WIFI).setSsid(TEST_SSID).build(); + .addTransportType(TRANSPORT_WIFI).setTransportInfo(mockWifiInfo).build(); return new NetworkStateSnapshot(TEST_NETWORK, networkCapabilities, prop, null /*subscriberId*/, TYPE_WIFI); } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index 6c4ae6fae2d2..62a2b1be139d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -306,12 +306,12 @@ public class PackageInstallerSessionTest { assertEquals(expected.stageCid, actual.stageCid); assertEquals(expected.isPrepared(), actual.isPrepared()); assertEquals(expected.isStaged(), actual.isStaged()); - assertEquals(expected.isStagedSessionApplied(), actual.isStagedSessionApplied()); - assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed()); - assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady()); - assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode()); - assertEquals(expected.getStagedSessionErrorMessage(), - actual.getStagedSessionErrorMessage()); + assertEquals(expected.isSessionApplied(), actual.isSessionApplied()); + assertEquals(expected.isSessionFailed(), actual.isSessionFailed()); + assertEquals(expected.isSessionReady(), actual.isSessionReady()); + assertEquals(expected.getSessionErrorCode(), actual.getSessionErrorCode()); + assertEquals(expected.getSessionErrorMessage(), + actual.getSessionErrorMessage()); assertEquals(expected.isPrepared(), actual.isPrepared()); assertEquals(expected.isCommitted(), actual.isCommitted()); assertEquals(expected.createdMillis, actual.createdMillis); 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 6c9f8fee25b0..9d672405603d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -48,6 +48,7 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; @@ -109,6 +110,8 @@ public class PackageManagerSettingsTests { @Mock DomainVerificationManagerInternal mDomainVerificationManager; + final ArrayMap<String, Long> mOrigFirstInstallTimes = new ArrayMap<>(); + @Before public void initializeMocks() { MockitoAnnotations.initMocks(this); @@ -208,14 +211,14 @@ public class PackageManagerSettingsTests { new WatchableTester(settingsUnderTest, "noSuspendingPackage"); watcher.register(); settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1)); - settingsUnderTest.readPackageRestrictionsLPr(0); + settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); watcher.verifyChangeReported("put package 1"); // Collect a snapshot at the midway point (package 2 has not been added) final Settings snapshot = settingsUnderTest.snapshot(); watcher.verifyNoChangeReported("snapshot"); settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2)); watcher.verifyChangeReported("put package 2"); - settingsUnderTest.readPackageRestrictionsLPr(0); + settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1); PackageUserStateInternal packageUserState1 = ps1.readUserState(0); @@ -251,7 +254,7 @@ public class PackageManagerSettingsTests { watcher.register(); settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1)); watcher.verifyChangeReported("put package 1"); - settingsUnderTest.readPackageRestrictionsLPr(0); + settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); watcher.verifyChangeReported("readPackageRestrictions"); final PackageSetting ps1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1); @@ -264,17 +267,17 @@ public class PackageManagerSettingsTests { final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0); watcher.verifyNoChangeReported("fetch user state"); assertThat(params, is(notNullValue())); - assertThat(params.appExtras.size(), is(1)); - assertThat(params.appExtras.getString("app_extra_string"), is("value")); - assertThat(params.launcherExtras.size(), is(1)); - assertThat(params.launcherExtras.getLong("launcher_extra_long"), is(4L)); - assertThat(params.dialogInfo, is(notNullValue())); - assertThat(params.dialogInfo.getDialogMessage(), is("Dialog Message")); - assertThat(params.dialogInfo.getTitleResId(), is(ID_NULL)); - assertThat(params.dialogInfo.getIconResId(), is(TEST_RESOURCE_ID)); - assertThat(params.dialogInfo.getNeutralButtonTextResId(), is(ID_NULL)); - assertThat(params.dialogInfo.getNeutralButtonAction(), is(BUTTON_ACTION_MORE_DETAILS)); - assertThat(params.dialogInfo.getDialogMessageResId(), is(ID_NULL)); + assertThat(params.getAppExtras().size(), is(1)); + assertThat(params.getAppExtras().getString("app_extra_string"), is("value")); + assertThat(params.getLauncherExtras().size(), is(1)); + assertThat(params.getLauncherExtras().getLong("launcher_extra_long"), is(4L)); + assertThat(params.getDialogInfo(), is(notNullValue())); + assertThat(params.getDialogInfo().getDialogMessage(), is("Dialog Message")); + assertThat(params.getDialogInfo().getTitleResId(), is(ID_NULL)); + assertThat(params.getDialogInfo().getIconResId(), is(TEST_RESOURCE_ID)); + assertThat(params.getDialogInfo().getNeutralButtonTextResId(), is(ID_NULL)); + assertThat(params.getDialogInfo().getNeutralButtonAction(), is(BUTTON_ACTION_MORE_DETAILS)); + assertThat(params.getDialogInfo().getDialogMessageResId(), is(ID_NULL)); } @Test @@ -338,7 +341,7 @@ public class PackageManagerSettingsTests { settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3)); watcher.verifyChangeReported("put package 3"); // now read and verify - settingsUnderTest.readPackageRestrictionsLPr(0); + settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); watcher.verifyChangeReported("readPackageRestrictions"); final PackageUserStateInternal readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1) .readUserState(0); @@ -351,18 +354,18 @@ public class PackageManagerSettingsTests { final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0); watcher.verifyNoChangeReported("read package param"); assertThat(params11, is(notNullValue())); - assertThat(params11.dialogInfo, is(dialogInfo1)); - assertThat(BaseBundle.kindofEquals(params11.appExtras, appExtras1), is(true)); - assertThat(BaseBundle.kindofEquals(params11.launcherExtras, launcherExtras1), + assertThat(params11.getDialogInfo(), is(dialogInfo1)); + assertThat(BaseBundle.kindofEquals(params11.getAppExtras(), appExtras1), is(true)); + assertThat(BaseBundle.kindofEquals(params11.getLauncherExtras(), launcherExtras1), is(true)); watcher.verifyNoChangeReported("read package param"); assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2")); final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1); assertThat(params12, is(notNullValue())); - assertThat(params12.dialogInfo, is(dialogInfo2)); - assertThat(BaseBundle.kindofEquals(params12.appExtras, appExtras2), is(true)); - assertThat(BaseBundle.kindofEquals(params12.launcherExtras, launcherExtras2), + assertThat(params12.getDialogInfo(), is(dialogInfo2)); + assertThat(BaseBundle.kindofEquals(params12.getAppExtras(), appExtras2), is(true)); + assertThat(BaseBundle.kindofEquals(params12.getLauncherExtras(), launcherExtras2), is(true)); watcher.verifyNoChangeReported("read package param"); @@ -373,9 +376,9 @@ public class PackageManagerSettingsTests { assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3")); final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0); assertThat(params21, is(notNullValue())); - assertThat(params21.dialogInfo, is(nullValue())); - assertThat(BaseBundle.kindofEquals(params21.appExtras, appExtras1), is(true)); - assertThat(params21.launcherExtras, is(nullValue())); + assertThat(params21.getDialogInfo(), is(nullValue())); + assertThat(BaseBundle.kindofEquals(params21.getAppExtras(), appExtras1), is(true)); + assertThat(params21.getLauncherExtras(), is(nullValue())); watcher.verifyNoChangeReported("read package param"); final PackageUserStateInternal readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3) @@ -388,7 +391,7 @@ public class PackageManagerSettingsTests { @Test public void testPackageRestrictionsSuspendedDefault() { final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1); - assertThat(defaultSetting.getSuspended(0), is(false)); + assertThat(defaultSetting.getUserStateOrDefault(0).isSuspended(), is(false)); } @Test @@ -418,7 +421,7 @@ public class PackageManagerSettingsTests { settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2)); settingsUnderTest.mPackages.put(PACKAGE_NAME_3, createPackageSetting(PACKAGE_NAME_3)); // now read and verify - settingsUnderTest.readPackageRestrictionsLPr(0); + settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); final PackageUserState readPus1 = settingsUnderTest.mPackages.get(PACKAGE_NAME_1) .readUserState(0); assertThat(readPus1.getDistractionFlags(), is(distractionFlags1)); @@ -1127,7 +1130,6 @@ public class PackageManagerSettingsTests { assertSame(origPkgSetting.getCpuAbiOverride(), testPkgSetting.getCpuAbiOverride()); assertThat(origPkgSetting.getCpuAbiOverride(), is(testPkgSetting.getCpuAbiOverride())); assertThat(origPkgSetting.getDomainSetId(), is(testPkgSetting.getDomainSetId())); - assertThat(origPkgSetting.getFirstInstallTime(), is(testPkgSetting.getFirstInstallTime())); assertSame(origPkgSetting.getInstallSource(), testPkgSetting.getInstallSource()); assertThat(origPkgSetting.isInstallPermissionsFixed(), is(testPkgSetting.isInstallPermissionsFixed())); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java index 828d419c02ff..1e4134ec25d8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java @@ -82,7 +82,8 @@ public class PackageUserStateTest { assertThat(testUserState.equals(oldUserState), is(false)); oldUserState = new PackageUserStateImpl(); - oldUserState.setSuspended(true); + oldUserState.putSuspendParams("suspendingPackage", + SuspendParams.getInstanceOrNull(null, new PersistableBundle(), null)); assertThat(testUserState.equals(oldUserState), is(false)); oldUserState = new PackageUserStateImpl(); @@ -231,7 +232,6 @@ public class PackageUserStateTest { final PackageUserStateImpl testUserState1 = new PackageUserStateImpl(); - testUserState1.setSuspended(true); testUserState1.setSuspendParams(paramsMap1); PackageUserStateImpl testUserState2 = diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java index 0708be2fb0c3..78bcf0c692b8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java @@ -147,6 +147,11 @@ public class ShortcutManagerTest12 extends BaseShortcutManagerTest { // Verifies pushDynamicShortcuts further persists shortcuts into AppSearch without // removing previous shortcuts when max number of shortcuts is reached. mManager.pushDynamicShortcut(makeShortcut("s6")); + // Increasing the max number of shortcuts since number of results per page in AppSearch + // is set to match the former. + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_MAX_SHORTCUTS + "=10," + + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); shortcuts = getAllPersistedShortcuts(); assertNotNull(shortcuts); assertEquals(6, shortcuts.size()); @@ -281,7 +286,7 @@ public class ShortcutManagerTest12 extends BaseShortcutManagerTest { private List<ShortcutInfo> getAllPersistedShortcuts() { try { - SystemClock.sleep(500); + SystemClock.sleep(5000); final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>(); getPersistedShortcut(future); return future.get(10, TimeUnit.SECONDS); diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index add4cdaa28ce..eeaf781dd307 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -357,13 +357,13 @@ public class SystemConfigTest { /** * Tests that readPermissions works correctly for a library using the new - * {@code updatable-library} tag. + * {@code apex-library} tag. */ @Test public void readPermissions_allowLibs_parsesUpdatableLibrary() throws IOException { String contents = "<permissions>\n" - + " <updatable-library \n" + + " <apex-library \n" + " name=\"foo\"\n" + " file=\"" + mFooJar + "\"\n" + " on-bootclasspath-before=\"10\"\n" diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 2e62286f2969..8a057f7b7065 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2311,17 +2311,15 @@ public class ActivityRecordTests extends WindowTestsBase { // Set initial orientation and update. activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); - mDisplayContent.updateOrientation( - mDisplayContent.getRequestedOverrideConfiguration(), - null /* freezeThisOneIfNeeded */, false /* forceUpdate */); + mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */, + false /* forceUpdate */); assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation()); appWindow.mResizeReported = false; // Update the orientation to perform 180 degree rotation and check that resize was reported. activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE); - mDisplayContent.updateOrientation( - mDisplayContent.getRequestedOverrideConfiguration(), - null /* freezeThisOneIfNeeded */, false /* forceUpdate */); + mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */, + false /* forceUpdate */); // In this test, DC will not get config update. Set the waiting flag to false. mDisplayContent.mWaitingForConfig = false; mWm.mRoot.performSurfacePlacement(); 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 506270657e42..0d6794685f09 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -678,6 +678,66 @@ public class AppTransitionControllerTest extends WindowTestsBase { opening, closing, false /* visible */)); } + @Test + public void testGetAnimationTargets_embeddedTask() { + // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, invisible) + // +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible) + final ActivityRecord activity1 = createActivityRecord(mDisplayContent); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + + final Task task2 = createTask(mDisplayContent); + task2.mRemoveWithTaskOrganizer = true; + final ActivityRecord activity2 = createActivityRecord(task2); + activity2.setVisible(false); + activity2.mVisibleRequested = true; + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + opening.add(activity2); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + + // No animation on the embedded task. + assertEquals( + new ArraySet<>(new WindowContainer[]{activity1.getTask()}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + + + @Test + public void testGetAnimationTargets_activityInEmbeddedTask() { + // [DisplayContent] - [Task] (embedded)-+- [ActivityRecord1] (opening, invisible) + // +- [ActivityRecord2] (closing, visible) + final Task task = createTask(mDisplayContent); + task.mRemoveWithTaskOrganizer = true; + + final ActivityRecord activity1 = createActivityRecord(task); + activity1.setVisible(false); + activity1.mVisibleRequested = true; + final ActivityRecord activity2 = createActivityRecord(task); + + final ArraySet<ActivityRecord> opening = new ArraySet<>(); + opening.add(activity1); + final ArraySet<ActivityRecord> closing = new ArraySet<>(); + closing.add(activity2); + + // Even though embedded task itself doesn't animate, activities in an embedded task + // animate. + assertEquals( + new ArraySet<>(new WindowContainer[]{activity1}), + AppTransitionController.getAnimationTargets( + opening, closing, true /* visible */)); + assertEquals( + new ArraySet<>(new WindowContainer[]{activity2}), + AppTransitionController.getAnimationTargets( + opening, closing, false /* visible */)); + } + static class TestRemoteAnimationRunner implements IRemoteAnimationRunner { @Override public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, diff --git a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java index 3714d9984a0c..f5d915dee257 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java @@ -39,6 +39,7 @@ import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Test for the splash screen exception list @@ -55,7 +56,16 @@ public class SplashScreenExceptionListTest { private DeviceConfig.Properties mInitialWindowManagerProperties; private final HandlerExecutor mExecutor = new HandlerExecutor( new Handler(Looper.getMainLooper())); - private final SplashScreenExceptionList mList = new SplashScreenExceptionList(mExecutor); + private final SplashScreenExceptionList mList = new SplashScreenExceptionList(mExecutor) { + @Override + void updateDeviceConfig(String rawList) { + super.updateDeviceConfig(rawList); + if (mOnUpdateDeviceConfig != null) { + mOnUpdateDeviceConfig.accept(rawList); + } + } + }; + private Consumer<String> mOnUpdateDeviceConfig; @Before public void setUp() throws Exception { @@ -91,13 +101,11 @@ public class SplashScreenExceptionListTest { private void setExceptionListAndWaitForCallback(String commaSeparatedList) { CountDownLatch latch = new CountDownLatch(1); - DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener = (p) -> { - if (commaSeparatedList.equals(p.getString(KEY_SPLASH_SCREEN_EXCEPTION_LIST, null))) { + mOnUpdateDeviceConfig = rawList -> { + if (commaSeparatedList.equals(rawList)) { latch.countDown(); } }; - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, - mExecutor, onPropertiesChangedListener); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_SPLASH_SCREEN_EXCEPTION_LIST, commaSeparatedList, false); try { @@ -105,8 +113,6 @@ public class SplashScreenExceptionListTest { latch.await(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { Assert.fail(e.getMessage()); - } finally { - DeviceConfig.removeOnPropertiesChangedListener(onPropertiesChangedListener); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java index a1d0eb8befcb..790b15443198 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java @@ -25,11 +25,10 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.server.wm.utils.CommonUtils.runWithShellPermissionIdentity; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import android.app.Activity; +import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.TaskDescription; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -57,7 +56,6 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import org.junit.After; -import org.junit.Before; import org.junit.Test; import java.util.Arrays; @@ -73,49 +71,42 @@ import java.util.function.Predicate; @MediumTest public class TaskStackChangedListenerTest { - private static final int VIRTUAL_DISPLAY_WIDTH = 800; - private static final int VIRTUAL_DISPLAY_HEIGHT = 600; - private static final int VIRTUAL_DISPLAY_DENSITY = 160; - private ITaskStackListener mTaskStackListener; - private DisplayManager mDisplayManager; private VirtualDisplay mVirtualDisplay; + private ImageReader mImageReader; private static final int WAIT_TIMEOUT_MS = 5000; private static final Object sLock = new Object(); - @Before - public void setUp() { - mDisplayManager = getInstrumentation().getContext().getSystemService( - DisplayManager.class); - mVirtualDisplay = createVirtualDisplay( - getClass().getSimpleName() + "_virtualDisplay", - VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT, VIRTUAL_DISPLAY_DENSITY); - } - @After public void tearDown() throws Exception { - ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener); - mTaskStackListener = null; - mVirtualDisplay.release(); + if (mTaskStackListener != null) { + ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener); + } + if (mVirtualDisplay != null) { + mVirtualDisplay.release(); + mImageReader.close(); + } } - private VirtualDisplay createVirtualDisplay(String name, int width, int height, int density) { - VirtualDisplay virtualDisplay = null; - try (ImageReader reader = ImageReader.newInstance(width, height, - /* format= */ PixelFormat.RGBA_8888, /* maxImages= */ 2)) { - int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY - | VIRTUAL_DISPLAY_FLAG_PUBLIC; - virtualDisplay = mDisplayManager.createVirtualDisplay( - name, width, height, density, reader.getSurface(), flags); - virtualDisplay.setSurface(reader.getSurface()); - } - assertTrue("display id must be unique", - virtualDisplay.getDisplay().getDisplayId() != Display.DEFAULT_DISPLAY); + private VirtualDisplay createVirtualDisplay() { + final int width = 800; + final int height = 600; + final int density = 160; + final DisplayManager displayManager = getInstrumentation().getContext().getSystemService( + DisplayManager.class); + mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, + 2 /* maxImages */); + final int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + | VIRTUAL_DISPLAY_FLAG_PUBLIC; + final String name = getClass().getSimpleName() + "_VirtualDisplay"; + mVirtualDisplay = displayManager.createVirtualDisplay(name, width, height, density, + mImageReader.getSurface(), flags); + mVirtualDisplay.setSurface(mImageReader.getSurface()); assertNotNull("display must be registered", - Arrays.asList(mDisplayManager.getDisplays()).stream().filter( + Arrays.stream(displayManager.getDisplays()).filter( d -> d.getName().equals(name)).findAny()); - return virtualDisplay; + return mVirtualDisplay; } @Test @@ -163,11 +154,10 @@ public class TaskStackChangedListenerTest { mTaskId = taskId; } @Override - public void onTaskDescriptionChanged(int taskId, TaskDescription td) - throws RemoteException { - if (mTaskId == taskId && !TextUtils.isEmpty(td.getLabel())) { - params[0] = taskId; - params[1] = td; + public void onTaskDescriptionChanged(RunningTaskInfo info) { + if (mTaskId == info.taskId && !TextUtils.isEmpty(info.taskDescription.getLabel())) { + params[0] = info.taskId; + params[1] = info.taskDescription; latch.countDown(); } } @@ -211,75 +201,71 @@ public class TaskStackChangedListenerTest { @Test @Presubmit public void testTaskChangeCallBacks() throws Exception { - final Object[] params = new Object[2]; final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1); final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1); final CountDownLatch taskRemovedLatch = new CountDownLatch(1); final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1); - final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1); + final int[] expectedTaskId = { -1 }; + final int[] receivedTaskId = { -1 }; + final ComponentName expectedName = new ComponentName(getInstrumentation().getContext(), + ActivityTaskChangeCallbacks.class); registerTaskStackChangedListener(new TaskStackListener() { @Override - public void onTaskCreated(int taskId, ComponentName componentName) - throws RemoteException { - params[0] = taskId; - params[1] = componentName; - taskCreatedLaunchLatch.countDown(); + public void onTaskCreated(int taskId, ComponentName componentName) { + receivedTaskId[0] = taskId; + if (expectedName.equals(componentName)) { + taskCreatedLaunchLatch.countDown(); + } } @Override - public void onTaskMovedToFront(int taskId) throws RemoteException { - params[0] = taskId; + public void onTaskMovedToFront(RunningTaskInfo info) { + receivedTaskId[0] = info.taskId; taskMovedToFrontLatch.countDown(); } @Override - public void onTaskRemovalStarted(int taskId) { - params[0] = taskId; - taskRemovalStartedLatch.countDown(); + public void onTaskRemovalStarted(RunningTaskInfo info) { + if (expectedTaskId[0] == info.taskId) { + taskRemovalStartedLatch.countDown(); + } } @Override - public void onTaskRemoved(int taskId) throws RemoteException { - if (taskCreatedLaunchLatch.getCount() == 1) { - // The test activity hasn't started. Ignore the noise from previous test. - return; + public void onTaskRemoved(int taskId) { + if (expectedTaskId[0] == taskId) { + taskRemovedLatch.countDown(); } - params[0] = taskId; - taskRemovedLatch.countDown(); } }); final ActivityTaskChangeCallbacks activity = (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class); - activity.setDetachedFromWindowLatch(onDetachedFromWindowLatch); - final int id = activity.getTaskId(); + expectedTaskId[0] = activity.getTaskId(); // Test for onTaskCreated and onTaskMovedToFront waitForCallback(taskMovedToFrontLatch); assertEquals(0, taskCreatedLaunchLatch.getCount()); - assertEquals(id, params[0]); - ComponentName componentName = (ComponentName) params[1]; - assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName()); + assertEquals(expectedTaskId[0], receivedTaskId[0]); + // Ensure that the window is attached before removal so there will be a detached callback. + waitForCallback(activity.mOnAttachedToWindowCountDownLatch); // Test for onTaskRemovalStarted. assertEquals(1, taskRemovalStartedLatch.getCount()); assertEquals(1, taskRemovedLatch.getCount()); activity.finishAndRemoveTask(); waitForCallback(taskRemovalStartedLatch); // onTaskRemovalStarted happens before the activity's window is removed. - assertFalse(activity.mOnDetachedFromWindowCalled); - assertEquals(id, params[0]); + assertEquals(1, activity.mOnDetachedFromWindowCountDownLatch.getCount()); // Test for onTaskRemoved. waitForCallback(taskRemovedLatch); - assertEquals(id, params[0]); - waitForCallback(onDetachedFromWindowLatch); - assertTrue(activity.mOnDetachedFromWindowCalled); + waitForCallback(activity.mOnDetachedFromWindowCountDownLatch); } @Test public void testTaskDisplayChanged() throws Exception { - int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId(); + final int virtualDisplayId = createVirtualDisplay().getDisplay().getDisplayId(); // Launch a Activity inside VirtualDisplay CountDownLatch displayChangedLatch1 = new CountDownLatch(1); @@ -498,17 +484,17 @@ public class TaskStackChangedListenerTest { } public static class ActivityTaskChangeCallbacks extends TestActivity { - public boolean mOnDetachedFromWindowCalled = false; - private CountDownLatch mOnDetachedFromWindowCountDownLatch; + final CountDownLatch mOnAttachedToWindowCountDownLatch = new CountDownLatch(1); + final CountDownLatch mOnDetachedFromWindowCountDownLatch = new CountDownLatch(1); @Override - public void onDetachedFromWindow() { - mOnDetachedFromWindowCalled = true; - mOnDetachedFromWindowCountDownLatch.countDown(); + public void onAttachedToWindow() { + mOnAttachedToWindowCountDownLatch.countDown(); } - void setDetachedFromWindowLatch(CountDownLatch countDownLatch) { - mOnDetachedFromWindowCountDownLatch = countDownLatch; + @Override + public void onDetachedFromWindow() { + mOnDetachedFromWindowCountDownLatch.countDown(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 0a8b2e7ddbb8..fe41734d0232 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -44,6 +44,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; +import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; import static com.google.common.truth.Truth.assertThat; @@ -66,6 +67,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.ComponentName; @@ -1424,6 +1426,29 @@ public class TaskTests extends WindowTestsBase { verify(task2).moveToFrontInner(anyString(), isNull()); } + @Test + public void testResumeTask_doNotResumeTaskFragmentBehindTranslucent() { + final Task task = createTask(mDisplayContent); + final TaskFragment tfBehind = createTaskFragmentWithParentTask( + task, false /* createEmbeddedTask */); + final TaskFragment tfFront = createTaskFragmentWithParentTask( + task, false /* createEmbeddedTask */); + spyOn(tfFront); + doReturn(true).when(tfFront).isTranslucent(any()); + + // TaskFragment behind another translucent TaskFragment should not be resumed. + assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, + tfBehind.getVisibility(null /* starting */)); + assertTrue(tfBehind.isFocusable()); + assertFalse(tfBehind.canBeResumed(null /* starting */)); + + spyOn(tfBehind); + task.resumeTopActivityUncheckedLocked(null /* prev */, ActivityOptions.makeBasic(), + false /* deferPause */); + + verify(tfBehind, never()).resumeTopActivity(any(), any(), anyBoolean()); + } + private Task getTestTask() { final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); return task.getBottomMostTask(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index ec6cd9249317..ed3888c7aedd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -556,8 +556,15 @@ public class TransitionTests extends WindowTestsBase { // The redrawn window will be faded in when the transition finishes. And because this test // only use one non-activity window, the fade rotation controller should also be cleared. - statusBar.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; + statusBar.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; + final SurfaceControl.Transaction postDrawTransaction = + mock(SurfaceControl.Transaction.class); + final boolean layoutNeeded = statusBar.finishDrawing(postDrawTransaction); + assertFalse(layoutNeeded); player.finish(); + // The controller should capture the draw transaction and merge it when preparing to run + // fade-in animation. + verify(mDisplayContent.getPendingTransaction()).merge(eq(postDrawTransaction)); assertNull(mDisplayContent.getFadeRotationAnimationController()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a985de50b9aa..34038c57eafc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -132,6 +132,9 @@ class WindowTestsBase extends SystemServiceTestsBase { // Default base activity name private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity"; + // An id appended to the end of the component name to make it unique + static int sCurrentActivityId = 0; + ActivityTaskManagerService mAtm; RootWindowContainer mRootWindowContainer; ActivityTaskSupervisor mSupervisor; @@ -895,13 +898,16 @@ class WindowTestsBase extends SystemServiceTestsBase { doReturn(100).when(hardwareBuffer).getHeight(); } + private static ComponentName getUniqueComponentName() { + return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, + DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++); + } + /** * Builder for creating new activities. */ protected static class ActivityBuilder { static final int DEFAULT_FAKE_UID = 12345; - // An id appended to the end of the component name to make it unique - private static int sCurrentActivityId = 0; private final ActivityTaskManagerService mService; @@ -1077,9 +1083,7 @@ class WindowTestsBase extends SystemServiceTestsBase { ActivityRecord buildInner() { if (mComponent == null) { - final int id = sCurrentActivityId++; - mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, - DEFAULT_COMPONENT_CLASS_NAME + id); + mComponent = getUniqueComponentName(); } Intent intent = new Intent(); @@ -1388,8 +1392,7 @@ class WindowTestsBase extends SystemServiceTestsBase { if (mIntent == null) { mIntent = new Intent(); if (mComponent == null) { - mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, - DEFAULT_COMPONENT_CLASS_NAME); + mComponent = getUniqueComponentName(); } mIntent.setComponent(mComponent); mIntent.setFlags(mFlags); @@ -1422,10 +1425,11 @@ class WindowTestsBase extends SystemServiceTestsBase { doNothing().when(rootTask).startActivityLocked( any(), any(), anyBoolean(), anyBoolean(), any(), any()); - // Create child task with activity. + // Create child activity. if (mCreateActivity) { new ActivityBuilder(mSupervisor.mService) .setTask(task) + .setComponent(mComponent) .build(); if (mOnTop) { // We move the task to front again in order to regain focus after activity diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index fabe612743bb..184e1541f1b0 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Bundle; @@ -261,6 +262,14 @@ public abstract class EuiccService extends Service { public static final String EXTRA_RESOLUTION_PORT_INDEX = "android.service.euicc.extra.RESOLUTION_PORT_INDEX"; + /** + * Intent extra set for resolution requests containing a bool indicating whether to use the + * given port index. For example, if {@link #switchToSubscription(int, PendingIntent)} is + * called, then no portIndex has been provided by the caller, and this extra will be false. + */ + public static final String EXTRA_RESOLUTION_USE_PORT_INDEX = + "android.service.euicc.extra.RESOLUTION_USE_PORT_INDEX"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "RESULT_" }, value = { @@ -852,14 +861,19 @@ public abstract class EuiccService extends Service { } @Override public void switchToSubscription(int slotId, int portIndex, String iccid, - boolean forceDeactivateSim, ISwitchToSubscriptionCallback callback) { + boolean forceDeactivateSim, ISwitchToSubscriptionCallback callback, + boolean usePortIndex) { mExecutor.execute(new Runnable() { @Override public void run() { - // TODO(b/207392528: use portIndex API once implemented) - int result = - EuiccService.this.onSwitchToSubscription( - slotId, iccid, forceDeactivateSim); + int result = 0; + if (usePortIndex) { + result = EuiccService.this.onSwitchToSubscriptionWithPort( + slotId, portIndex, iccid, forceDeactivateSim); + } else { + result = EuiccService.this.onSwitchToSubscription( + slotId, iccid, forceDeactivateSim); + } try { callback.onComplete(result); } catch (RemoteException e) { diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl index aa30c9e88462..030e11aee993 100644 --- a/telephony/java/android/service/euicc/IEuiccService.aidl +++ b/telephony/java/android/service/euicc/IEuiccService.aidl @@ -49,7 +49,7 @@ oneway interface IEuiccService { void getEuiccInfo(int slotId, in IGetEuiccInfoCallback callback); void deleteSubscription(int slotId, String iccid, in IDeleteSubscriptionCallback callback); void switchToSubscription(int slotId, int portIndex, String iccid, boolean forceDeactivateSim, - in ISwitchToSubscriptionCallback callback); + in ISwitchToSubscriptionCallback callback, boolean useLegacyApi); void updateSubscriptionNickname(int slotId, String iccid, String nickname, in IUpdateSubscriptionNicknameCallback callback); void eraseSubscriptions(int slotId, in IEraseSubscriptionsCallback callback); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 7d17894ae1bf..cd399c02a72e 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -798,6 +798,14 @@ public class CarrierConfigManager { "carrier_cross_sim_ims_available_bool"; /** + * Flag specifying whether cross sim calling on opportunistic data is supported for carrier. + * When {@code false} the carrier does not support cross sim calling on opportunistic data. + * When {@code true} the carrier does support cross sim calling on opportunistic data. + */ + public static final String KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL = + "enable_cross_sim_calling_on_opportunistic_data_bool"; + + /** * Specifies a map from dialstrings to replacements for roaming network service numbers which * cannot be replaced on the carrier side. * <p> @@ -4181,6 +4189,56 @@ public class CarrierConfigManager { "gba_ua_tls_cipher_suite_int"; /** + * The data stall recovery timers array in milliseconds, each element is the delay before + * performining next recovery action. + * + * The default value of timers array are: [180000ms, 180000ms, 180000ms] (3 minutes) + * Array[0]: It's the timer between RECOVERY_ACTION GET_DATA_CALL_LIST and CLEANUP, if data + * stall symptom still occurred, it will perform next recovery action after 180000ms. + * Array[1]: It's the timer between RECOVERY_ACTION CLEANUP and RADIO_RESTART, if data stall + * symptom still occurred, it will perform next recovery action after 180000ms. + * Array[2]: It's the timer between RECOVERY_ACTION RADIO_RESTART and RESET_MODEM, if data stall + * symptom still occurred, it will perform next recovery action after 180000ms. + * + * See the {@code RECOVERY_ACTION_*} constants in + * {@link com.android.internal.telephony.data.DataStallRecoveryManager} + * @hide + */ + public static final String KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY = + "data_stall_recovery_timers_long_array"; + + /** + * The data stall recovery action boolean array, we use this array to determine if the + * data stall recovery action needs to be skipped. + * + * For example, if the carrier use the same APN for both of IA and default type, + * the data call will not disconnect in modem side (so the RECOVERY_ACTION_CLEANUP + * did not effect). In this case, we can config the boolean variable of action + * RECOVERY_ACTION_CLEANUP to true, then it can be ignored to speed up the recovery + * action procedure. + * + * The default value of boolean array are: [false, false, false, false] + * Array[0]: When performing the recovery action, we can use this boolean value to determine + * if we need to perform RECOVERY_ACTION_GET_DATA_CALL_LIST. + * Array[1]: If data stall symptom still occurred, we can use this boolean value to determine + * if we need to perform RECOVERY_ACTION_CLEANUP. For example, if the carrier use the same APN + * for both of IA and default type, the data call will not disconnect in modem side + * (so the RECOVERY_ACTION_CLEANUP did not effect). In this case, we can config the boolean + * variable of action RECOVERY_ACTION_CLEANUP to true, then it can be ignored to speed up the + * recovery action procedure. + * Array[2]: If data stall symptom still occurred, we can use this boolean value to determine + * if we need to perform RECOVERY_ACTION_RADIO_RESTART. + * Array[3]: If data stall symptom still occurred, we can use this boolean value to determine + * if we need to perform RECOVERY_ACTION_MODEM_RESET. + * + * See the {@code RECOVERY_ACTION_*} constants in + * {@link com.android.internal.telephony.data.DataStallRecoveryManager} + * @hide + */ + public static final String KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY = + "data_stall_recovery_should_skip_bool_array"; + + /** * Configs used by ImsServiceEntitlement. */ public static final class ImsServiceEntitlement { @@ -5537,6 +5595,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false); @@ -6172,6 +6231,11 @@ public class CarrierConfigManager { + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"}); sDefaults.putInt(KEY_CELLULAR_USAGE_SETTING_INT, SubscriptionManager.USAGE_SETTING_UNKNOWN); + // Default data stall recovery configurations. + sDefaults.putLongArray(KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY, + new long[] {180000, 180000, 180000}); + sDefaults.putBooleanArray(KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY, + new boolean[] {false, false, false, false}); } /** diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 389cc6dba3bf..2e5402c53d73 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -16,7 +16,6 @@ package android.telephony.euicc; import android.Manifest; -import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,7 +29,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; -import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.telephony.TelephonyFrameworkInitializer; @@ -38,13 +36,11 @@ import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccCardManager.ResetOption; import com.android.internal.telephony.euicc.IEuiccController; -import com.android.internal.telephony.euicc.IResultCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; -import java.util.concurrent.Executor; import java.util.stream.Collectors; /** @@ -221,20 +217,6 @@ public class EuiccManager { "android.telephony.euicc.action.START_EUICC_ACTIVATION"; /** - * Result codes passed to the ResultListener by - * {@link #switchToSubscription(int, int, Executor, ResultListener)} - * - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"EMBEDDED_SUBSCRIPTION_RESULT_"}, value = { - EMBEDDED_SUBSCRIPTION_RESULT_OK, - EMBEDDED_SUBSCRIPTION_RESULT_ERROR, - EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR - }) - public @interface ResultCode{} - - /** * Result code for an operation indicating that the operation succeeded. */ public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0; @@ -1147,7 +1129,7 @@ public class EuiccManager { * @param callbackIntent a PendingIntent to launch when the operation completes. * * @deprecated From T, callers should use - * {@link #switchToSubscription(int, int, Executor, ResultListener)} instead to specify a port + * {@link #switchToSubscription(int, int, PendingIntent)} instead to specify a port * index on the card to switch to. */ @Deprecated @@ -1190,47 +1172,24 @@ public class EuiccManager { * permission, or the calling app must be authorized to manage the active subscription on * the target eUICC. * @param portIndex the index of the port to target for the enabled subscription - * @param executor an Executor on which to run the callback - * @param callback a {@link ResultListener} which will run when the operation completes + * @param callbackIntent a PendingIntent to launch when the operation completes. */ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void switchToSubscription(int subscriptionId, int portIndex, - @NonNull @CallbackExecutor Executor executor, - @NonNull ResultListener callback) { + @NonNull PendingIntent callbackIntent) { if (!isEnabled()) { - sendUnavailableErrorToCallback(executor, callback); + sendUnavailableError(callbackIntent); return; } try { - IResultCallback internalCallback = new IResultCallback.Stub() { - @Override - public void onComplete(int result, Intent resultIntent) { - executor.execute(() -> Binder.withCleanCallingIdentity( - () -> callback.onComplete(result, resultIntent))); - } - }; - getIEuiccController().switchToSubscriptionWithPort(mCardId, portIndex, - subscriptionId, mContext.getOpPackageName(), internalCallback); + getIEuiccController().switchToSubscriptionWithPort(mCardId, + subscriptionId, portIndex, mContext.getOpPackageName(), callbackIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Callback to receive the result of an EuiccManager API. - */ - public interface ResultListener { - /** - * Called on completion of some operation. - * @param resultCode representing success or specific failure of the operation - * (See {@link ResultCode}) - * @param resultIntent an intent used to start a resolution activity when an error - * occurs that can be resolved by the user - */ - void onComplete(@ResultCode int resultCode, @Nullable Intent resultIntent); - } - - /** * Update the nickname for the given subscription. * * <p>Requires that the calling app has carrier privileges according to the metadata of the @@ -1501,13 +1460,6 @@ public class EuiccManager { } } - private static void sendUnavailableErrorToCallback(@NonNull Executor executor, - ResultListener callback) { - Integer result = EMBEDDED_SUBSCRIPTION_RESULT_ERROR; - executor.execute(() -> - Binder.withCleanCallingIdentity(() -> callback.onComplete(result, null))); - } - private static IEuiccController getIEuiccController() { return IEuiccController.Stub.asInterface( TelephonyFrameworkInitializer diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index 7f5982f128e3..dda95b159552 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -22,8 +22,6 @@ import android.os.Bundle; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; -import com.android.internal.telephony.euicc.IResultCallback; - import java.util.List; /** @hide */ @@ -45,8 +43,8 @@ interface IEuiccController { in PendingIntent callbackIntent); oneway void switchToSubscription(int cardId, int subscriptionId, String callingPackage, in PendingIntent callbackIntent); - oneway void switchToSubscriptionWithPort(int cardId, int portIndex, int subscriptionId, - String callingPackage, in IResultCallback callback); + oneway void switchToSubscriptionWithPort(int cardId, int subscriptionId, int portIndex, + String callingPackage, in PendingIntent callbackIntent); oneway void updateSubscriptionNickname(int cardId, int subscriptionId, String nickname, String callingPackage, in PendingIntent callbackIntent); oneway void eraseSubscriptions(int cardId, in PendingIntent callbackIntent); diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk index ce5e4cf46695..284008c5be71 100644 --- a/test-legacy/Android.mk +++ b/test-legacy/Android.mk @@ -41,6 +41,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ include $(BUILD_STATIC_JAVA_LIBRARY) # Archive a copy of the classes.jar in SDK build. -$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) +$(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt index 59e8dc826007..8fe00297139a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -37,16 +37,21 @@ class TwoActivitiesAppHelper @JvmOverloads constructor( .launcherStrategy ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) { - val button = device.wait( - Until.findObject(By.res(getPackage(), "launch_second_activity")), - FIND_TIMEOUT) + val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY) + val button = device.wait(Until.findObject(launchActivityButton), FIND_TIMEOUT) require(button != null) { "Button not found, this usually happens when the device " + "was left in an unknown state (e.g. in split screen)" } button.click() + + device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT) wmHelper.waitForAppTransitionIdle() wmHelper.waitForFullScreenApp(component) } + + companion object { + private const val LAUNCH_SECOND_ACTIVITY = "launch_second_activity" + } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt index a7a9fe289a4a..19e2c92304d4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt @@ -86,8 +86,8 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame // [Step1]: Swipe right from imeTestApp to testApp task createTag(TAG_IME_VISIBLE) val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) - device.swipe(0, displayBounds.bounds.height(), - displayBounds.bounds.width(), displayBounds.bounds.height(), 50) + device.swipe(0, displayBounds.bounds.height, + displayBounds.bounds.width, displayBounds.bounds.height, 50) wmHelper.waitForFullScreenApp(testApp.component) wmHelper.waitForAppTransitionIdle() @@ -96,8 +96,8 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame transitions { // [Step2]: Swipe left to back to imeTestApp task val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) - device.swipe(displayBounds.bounds.width(), displayBounds.bounds.height(), - 0, displayBounds.bounds.height(), 50) + device.swipe(displayBounds.bounds.width, displayBounds.bounds.height, + 0, displayBounds.bounds.height, 50) wmHelper.waitForFullScreenApp(imeTestApp.component) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 769cb1ad959b..882e128fe181 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -148,22 +148,28 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) testSpec.assertLayers { - this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) + this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { + it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds) + } .isInvisible(bgColorLayer) .then() // Transitioning .isVisible(bgColorLayer) .then() // Fully transitioned to simple SIMPLE_ACTIVITY - .coversExactly(displayBounds, SIMPLE_ACTIVITY) + .invoke("SIMPLE_ACTIVITY coversExactly displayBounds") { + it.visibleRegion(SIMPLE_ACTIVITY).coversExactly(displayBounds) + } .isInvisible(bgColorLayer) .then() // Transitioning back .isVisible(bgColorLayer) .then() // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY + .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { + it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds) + } .isInvisible(bgColorLayer) - .coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) } } diff --git a/tests/HwAccelerationTest/Android.bp b/tests/HwAccelerationTest/Android.bp index e618ed1e3ab9..51848f2857c9 100644 --- a/tests/HwAccelerationTest/Android.bp +++ b/tests/HwAccelerationTest/Android.bp @@ -32,6 +32,10 @@ android_test { "**/*.java", "**/*.kt", ], + static_libs: [ + "androidx.cardview_cardview", + ], + platform_apis: true, certificate: "platform", } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 22fe4242fbbb..b0ccbd1cf22f 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -789,6 +789,15 @@ </intent-filter> </activity> + <activity android:name="RenderEffectViewActivity" + android:label="RenderEffect/View" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="StretchShaderActivity" android:label="RenderEffect/Stretch" android:exported="true"> diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png b/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png Binary files differnew file mode 100644 index 000000000000..cc8adf15f4f0 --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable-nodpi/scratches.png diff --git a/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg b/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg Binary files differnew file mode 100644 index 000000000000..b5aff104207a --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable-nodpi/weather_2.jpg diff --git a/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml b/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml new file mode 100644 index 000000000000..b91377d1ab49 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/view_runtime_shader.xml @@ -0,0 +1,205 @@ +<?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. + --> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <LinearLayout + android:id="@+id/TopLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="8dp" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="Sample Card #1"/> + + <androidx.cardview.widget.CardView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:layout_marginBottom="16dp" + android:clickable="true" + android:focusable="true" + android:minHeight="148dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:paddingBottom="8dp"> + + <ImageView + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:contentDescription="Logo" + android:src="@drawable/icon"/> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1.0" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:orientation="vertical"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textStyle="bold" + android:text="Image Transition"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ellipsize="end" + android:maxLines="1" + android:text="Touch the image to trigger the animation"/> + </LinearLayout> + </LinearLayout> + + <com.android.test.hwui.BitmapTransitionView + android:layout_width="match_parent" + android:layout_height="194dp" + android:padding="8dp"/> + + </LinearLayout> + + </androidx.cardview.widget.CardView> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="Sample Card #2"/> + + <androidx.cardview.widget.CardView + android:id="@+id/CardView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:layout_marginBottom="16dp" + android:clickable="true" + android:focusable="true" + android:minHeight="148dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:paddingBottom="8dp"> + + <ImageView + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" + android:contentDescription="Logo" + android:src="@drawable/icon"/> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1.0" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:orientation="vertical"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textStyle="bold" + android:text="View Group Manipulation"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:ellipsize="end" + android:maxLines="1" + android:text="Tap the card to trigger the animation"/> + </LinearLayout> + </LinearLayout> + + <ImageView + android:layout_width="match_parent" + android:layout_height="194dp" + android:background="@android:color/transparent" + android:src="@drawable/weather_2"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:paddingBottom="8dp" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <RatingBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="Card Rating" + android:isIndicator="true" + android:numStars="5" + android:rating="4.5" + + android:stepSize="0.5"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:text="Category 4.5 Storm"/> + </LinearLayout> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:maxLines="3" + android:textIsSelectable="true" + android:text="Lorem ipsum dolor sit amet, nec no nominavi scaevola. Per et + sint sapientem, nobis perpetua salutandi mei te. Quo tamquam probatus + reprehendunt in. Eos esse purto eruditi ea. Enim tation persius ut sea, + eos ad consul populo. Ne eum solet altera. Cibo eligendi et est, electram + theophrastus te vel eu."/> + + </LinearLayout> + + </LinearLayout> + + </androidx.cardview.widget.CardView> + </LinearLayout> +</ScrollView> diff --git a/tests/HwAccelerationTest/res/values/styles.xml b/tests/HwAccelerationTest/res/values/styles.xml index fa5437f38ace..55f4dd697907 100644 --- a/tests/HwAccelerationTest/res/values/styles.xml +++ b/tests/HwAccelerationTest/res/values/styles.xml @@ -41,4 +41,5 @@ <item name="android:spotShadowAlpha">1</item> --> </style> + </resources> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt new file mode 100644 index 000000000000..d3ad9e8193c0 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapTransitionView.kt @@ -0,0 +1,138 @@ +/* + * 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.test.hwui + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.BitmapShader +import android.graphics.Canvas +import android.graphics.ImageDecoder +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.RuntimeShader +import android.graphics.Shader +import android.util.AttributeSet +import android.view.View + +class BitmapTransitionView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + private val mPaint = Paint() + private val mImageA = ImageDecoder.decodeBitmap( + ImageDecoder.createSource(context.resources, R.drawable.large_photo)) + private val mImageB = ImageDecoder.decodeBitmap( + ImageDecoder.createSource(context.resources, R.drawable.very_large_photo)) + private val mShaderA = BitmapShader(mImageA, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + private val mShaderB = BitmapShader(mImageB, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + private val mShader = RuntimeShader(AGSL, false) + private var mCurrentProgress = -1f + private var mForwardProgress = true + private var mCurrentAnimator = ValueAnimator.ofFloat(-1f, 1f) + + init { + isClickable = true + + mCurrentAnimator.duration = 1500 + mCurrentAnimator.addUpdateListener { animation -> + mCurrentProgress = animation.animatedValue as Float + postInvalidate() + } + } + + override fun performClick(): Boolean { + if (super.performClick()) return true + + if (mCurrentAnimator.isRunning) { + mCurrentAnimator.reverse() + return true + } + + if (mForwardProgress) { + mCurrentAnimator.setFloatValues(-1f, 1f) + mForwardProgress = false + } else { + mCurrentAnimator.setFloatValues(1f, -1f) + mForwardProgress = true + } + + mCurrentAnimator.start() + postInvalidate() + return true + } + + override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { + val matrixA = Matrix() + val matrixB = Matrix() + + matrixA.postScale(width.toFloat() / mImageA.width, height.toFloat() / mImageA.height) + matrixB.postScale(width.toFloat() / mImageB.width, height.toFloat() / mImageB.height) + + mShaderA.setLocalMatrix(matrixA) + mShaderB.setLocalMatrix(matrixB) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + mShader.setInputShader("imageA", mShaderA) + mShader.setInputShader("imageB", mShaderB) + mShader.setIntUniform("imageDimensions", width, height) + mShader.setFloatUniform("progress", mCurrentProgress) + + mPaint.shader = mShader + canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), mPaint) + } + + private companion object { + const val AGSL = """ + uniform shader imageA; + uniform shader imageB; + uniform ivec2 imageDimensions; + uniform float progress; + + const vec2 iSize = vec2(48.0, 48.0); + const float iDir = 0.5; + const float iRand = 0.81; + + float hash12(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * .1031); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); + } + + float ramp(float2 p) { + return mix(hash12(p), + dot(p/vec2(imageDimensions), float2(iDir, 1 - iDir)), + iRand); + } + + half4 main(float2 p) { + float2 lowRes = p / iSize; + float2 cellCenter = (floor(lowRes) + 0.5) * iSize; + float2 posInCell = fract(lowRes) * 2 - 1; + + float v = ramp(cellCenter) + progress; + float distToCenter = max(abs(posInCell.x), abs(posInCell.y)); + + return distToCenter > v ? imageA.eval(p).rgb1 : imageB.eval(p).rgb1; + } + """ + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt new file mode 100644 index 000000000000..06280d295425 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RenderEffectViewActivity.kt @@ -0,0 +1,109 @@ +/* + * 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.test.hwui + +import android.animation.ValueAnimator +import android.app.Activity +import android.graphics.Bitmap +import android.graphics.BitmapShader +import android.graphics.ImageDecoder +import android.graphics.Matrix +import android.graphics.Shader +import android.graphics.RenderEffect +import android.graphics.RuntimeShader +import android.os.Bundle +import android.view.View + +class RenderEffectViewActivity : Activity() { + + private val mDropsShader = RuntimeShader(dropsAGSL, false) + private var mDropsAnimator = ValueAnimator.ofFloat(0f, 1f) + private var mStartTime = System.currentTimeMillis() + private lateinit var mScratchesImage: Bitmap + private lateinit var mScratchesShader: BitmapShader + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.view_runtime_shader) + + val dropsView = findViewById<View>(R.id.CardView)!! + dropsView.isClickable = true + dropsView.setOnClickListener { + if (mDropsAnimator.isRunning) { + mDropsAnimator.cancel() + dropsView.setRenderEffect(null) + } else { + mDropsAnimator.start() + } + } + + val imgSource = ImageDecoder.createSource(resources, R.drawable.scratches) + mScratchesImage = ImageDecoder.decodeBitmap(imgSource) + mScratchesShader = BitmapShader(mScratchesImage, + Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) + + mDropsAnimator.duration = 1000 + mDropsAnimator.repeatCount = ValueAnimator.INFINITE + mDropsAnimator.addUpdateListener { _ -> + val viewWidth = dropsView.width.toFloat() + val viewHeight = dropsView.height.toFloat() + val scratchesMatrix = Matrix() + scratchesMatrix.postScale(viewWidth / mScratchesImage.width, + viewHeight / mScratchesImage.height) + mScratchesShader.setLocalMatrix(scratchesMatrix) + + mDropsShader.setInputShader("scratches", mScratchesShader) + mDropsShader.setFloatUniform("elapsedSeconds", + (System.currentTimeMillis() - mStartTime) / 1000f) + mDropsShader.setFloatUniform("viewDimensions", viewWidth, viewHeight) + + val dropsEffect = RenderEffect.createRuntimeShaderEffect(mDropsShader, "background") + val blurEffect = RenderEffect.createBlurEffect(10f, 10f, Shader.TileMode.CLAMP) + + dropsView.setRenderEffect(RenderEffect.createChainEffect(dropsEffect, blurEffect)) + } + } + + private companion object { + const val dropsAGSL = """ + uniform float elapsedSeconds; + uniform vec2 viewDimensions; + uniform shader background; + uniform shader scratches; + + vec2 dropsUV(vec2 fragCoord ) { + vec2 uv = fragCoord.xy / viewDimensions.xy; // 0 <> 1 + vec2 offs = vec2(0.); + return (offs + uv).xy; + } + + const vec3 iFrostColorRGB = vec3(0.5, 0.5, 0.5); + const float iFrostColorAlpha = .3; + + half4 main(float2 fragCoord) { + half4 bg = background.eval(dropsUV(fragCoord)*viewDimensions.xy); + float2 scratchCoord = fragCoord.xy / viewDimensions.xy;; + scratchCoord += 1.5; + scratchCoord = mod(scratchCoord, 1); + half scratch = scratches.eval(scratchCoord*viewDimensions.xy).r; + bg.rgb = mix(bg.rgb, iFrostColorRGB, iFrostColorAlpha); + bg.rgb = mix(bg.rgb, half3(1), pow(scratch,3)); + return bg; + } + """ + } +}
\ No newline at end of file diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index b21d7b5d1f4a..426f3beafd5a 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -303,11 +303,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); } @Test @@ -315,11 +318,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false)); } @Test @@ -327,11 +333,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false).setStaged()); } @Test @@ -339,11 +348,14 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion("test.apex.rebootless")).isEqualTo(1); TestApp apex = new TestApp("apex", "test.apex.rebootless", 2, /* isApex= */ true, "test.rebootless_apex_v2.apex"); + String expectedFailMessage = "Update of APEX package test.apex.rebootless is not allowed " + + "for com.android.tests.stagedinstallinternal"; InstallUtils.commitExpectingFailure( - AssertionError.class, - "Update of APEX package test.apex.rebootless is not allowed " - + "for com.android.tests.stagedinstallinternal", + AssertionError.class, expectedFailMessage, Install.single(apex).setBypassAllowedApexUpdateCheck(false)); + InstallUtils.commitExpectingFailure( + AssertionError.class, expectedFailMessage, + Install.multi(apex).setBypassAllowedApexUpdateCheck(false)); } @Test @@ -467,8 +479,7 @@ public class StagedInstallInternalTest { // Query proper module name result = getPackageManagerNative().getStagedApexInfo(TEST_APEX_PACKAGE_NAME); assertThat(result.moduleName).isEqualTo(TEST_APEX_PACKAGE_NAME); - assertThat(result.hasBootClassPathJars).isTrue(); - assertThat(result.hasSystemServerClassPathJars).isTrue(); + assertThat(result.hasClassPathJars).isTrue(); InstallUtils.openPackageInstallerSession(sessionId).abandon(); } diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index 68e213b2c00f..fe2b0186c57f 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -13,15 +13,18 @@ from fontTools import ttLib EMOJI_VS = 0xFE0F LANG_TO_SCRIPT = { + 'af': 'Latn', 'as': 'Beng', 'am': 'Latn', 'be': 'Cyrl', 'bg': 'Cyrl', 'bn': 'Beng', + 'cs': 'Latn', 'cu': 'Cyrl', 'cy': 'Latn', 'da': 'Latn', 'de': 'Latn', + 'el': 'Latn', 'en': 'Latn', 'es': 'Latn', 'et': 'Latn', @@ -36,19 +39,24 @@ LANG_TO_SCRIPT = { 'hy': 'Armn', 'it': 'Latn', 'ja': 'Jpan', + 'ka': 'Latn', 'kn': 'Knda', 'ko': 'Kore', 'la': 'Latn', 'lt': 'Latn', + 'lv': 'Latn', 'ml': 'Mlym', 'mn': 'Cyrl', 'mr': 'Deva', 'nb': 'Latn', + 'nl': 'Latn', 'nn': 'Latn', 'or': 'Orya', 'pa': 'Guru', 'pt': 'Latn', + 'sk': 'Latn', 'sl': 'Latn', + 'sq': 'Latn', 'ta': 'Taml', 'te': 'Telu', 'tk': 'Latn', |