diff options
180 files changed, 4890 insertions, 1406 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 088cadba89ab..8a9c774117d3 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -896,7 +896,7 @@ public class JobInfo implements Parcelable { * @param flags Flags for the observer. */ public TriggerContentUri(@NonNull Uri uri, @Flags int flags) { - mUri = uri; + mUri = Objects.requireNonNull(uri); mFlags = flags; } diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java index 1072406d26cc..7833a037463c 100644 --- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java @@ -16,6 +16,7 @@ package com.android.server.job; +import android.annotation.NonNull; import android.app.job.JobInfo; import android.util.proto.ProtoOutputStream; @@ -44,6 +45,10 @@ public interface JobSchedulerInternal { void removeBackingUpUid(int uid); void clearAllBackingUpUids(); + /** Returns the package responsible for backing up media on the device. */ + @NonNull + String getMediaBackupPackage(); + /** * The user has started interacting with the app. Take any appropriate action. */ diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index e4c6b52f94bb..ff7944d07310 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -77,6 +77,7 @@ import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.ArrayUtils; @@ -248,6 +249,9 @@ public class JobSchedulerService extends com.android.server.SystemService */ private final List<JobRestriction> mJobRestrictions; + @NonNull + private final String mSystemGalleryPackage; + private final CountQuotaTracker mQuotaTracker; private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; @@ -1394,6 +1398,9 @@ public class JobSchedulerService extends com.android.server.SystemService mJobRestrictions = new ArrayList<>(); mJobRestrictions.add(new ThermalStatusRestriction(this)); + mSystemGalleryPackage = Objects.requireNonNull( + context.getString(R.string.config_systemGallery)); + // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. if (!mJobs.jobTimesInflatedValid()) { @@ -2359,6 +2366,11 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override + public String getMediaBackupPackage() { + return mSystemGalleryPackage; + } + + @Override public void reportAppUsage(String packageName, int userId) { JobSchedulerService.this.reportAppUsage(packageName, userId); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java index a775cf5a671c..5fcd774189ac 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java @@ -344,7 +344,7 @@ public final class ContentObserverController extends StateController { mContext.getContentResolver().unregisterContentObserver(obs); ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observerOfUser = mObservers.get(obs.mUserId); - if (observerOfUser != null) { + if (observerOfUser != null) { observerOfUser.remove(obs.mUri); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 1e89158ca4bb..cf7f3804b34d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -19,6 +19,7 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; +import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.app.AppGlobals; @@ -30,6 +31,7 @@ import android.net.Network; import android.net.Uri; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.MediaStore; import android.text.format.DateFormat; import android.util.ArraySet; import android.util.Pair; @@ -207,6 +209,18 @@ public final class JobStatus { */ private int mDynamicConstraints = 0; + /** + * Indicates whether the job is responsible for backing up media, so we can be lenient in + * applying standby throttling. + * + * Doesn't exempt jobs with a deadline constraint, as they can be started without any content or + * network changes, in which case this exemption does not make sense. + * + * TODO(b/149519887): Use a more explicit signal, maybe an API flag, that the scheduling package + * needs to provide at the time of scheduling a job. + */ + private final boolean mHasMediaBackupExemption; + // Set to true if doze constraint was satisfied due to app being whitelisted. public boolean dozeWhitelisted; @@ -415,9 +429,11 @@ public final class JobStatus { this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis; this.numFailures = numFailures; + boolean requiresNetwork = false; int requiredConstraints = job.getConstraintFlags(); if (job.getRequiredNetwork() != null) { requiredConstraints |= CONSTRAINT_CONNECTIVITY; + requiresNetwork = true; } if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) { requiredConstraints |= CONSTRAINT_TIMING_DELAY; @@ -425,8 +441,16 @@ public final class JobStatus { if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) { requiredConstraints |= CONSTRAINT_DEADLINE; } + boolean mediaOnly = false; if (job.getTriggerContentUris() != null) { requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; + mediaOnly = true; + for (JobInfo.TriggerContentUri uri : job.getTriggerContentUris()) { + if (!MediaStore.AUTHORITY.equals(uri.getUri().getAuthority())) { + mediaOnly = false; + break; + } + } } this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; @@ -450,6 +474,9 @@ public final class JobStatus { // our source UID into place. job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid); } + final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class); + mHasMediaBackupExemption = !job.hasLateConstraint() && mediaOnly && requiresNetwork + && this.sourcePackageName.equals(jsi.getMediaBackupPackage()); } /** Copy constructor: used specifically when cloning JobStatus objects for persistence, @@ -545,7 +572,6 @@ public final class JobStatus { int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage, sourceUserId, elapsedNow); - JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class); return new JobStatus(job, callingUid, sourcePkg, sourceUserId, standbyBucket, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, @@ -734,7 +760,14 @@ public final class JobStatus { // like other ACTIVE apps. return ACTIVE_INDEX; } - return getStandbyBucket(); + final int actualBucket = getStandbyBucket(); + if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX + && mHasMediaBackupExemption) { + // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the + // source package may not have been used directly in a while. + return Math.min(WORKING_INDEX, actualBucket); + } + return actualBucket; } /** Returns the real standby bucket of the job. */ diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp index c0f84a0ba070..baa1c251324f 100644 --- a/apex/statsd/Android.bp +++ b/apex/statsd/Android.bp @@ -20,6 +20,7 @@ apex { apex_defaults { native_shared_libs: [ + "libstatssocket", "libstatspull", "libstats_jni", ], @@ -76,4 +77,4 @@ cc_library_shared { //TODO (b/148620413): remove platform. "//apex_available:platform", ], -}
\ No newline at end of file +} diff --git a/api/current.txt b/api/current.txt index 4887c66660bb..5e8974fb82d7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -289,6 +289,7 @@ package android { field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 field public static final int allowEmbedded = 16843765; // 0x10103f5 + field public static final int allowNativeHeapPointerTagging = 16844311; // 0x1010617 field public static final int allowParallelSyncs = 16843570; // 0x1010332 field public static final int allowSingleTap = 16843353; // 0x1010259 field public static final int allowTaskReparenting = 16843268; // 0x1010204 @@ -2876,6 +2877,7 @@ package android.accessibilityservice { method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.accessibilityservice.AccessibilityService.ScreenshotResult>); field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14 + field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28 field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13 field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b @@ -2883,6 +2885,7 @@ package android.accessibilityservice { field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19 field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15 field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17 + field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29 field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16 field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f @@ -2890,6 +2893,7 @@ package android.accessibilityservice { field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18 field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26 + field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25 field public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34; // 0x22 field public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35; // 0x23 @@ -2954,7 +2958,7 @@ package android.accessibilityservice { } public static final class AccessibilityService.ScreenshotResult { - method @Nullable public android.graphics.ColorSpace getColorSpace(); + method @NonNull public android.graphics.ColorSpace getColorSpace(); method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer(); method public long getTimestamp(); } @@ -7238,7 +7242,7 @@ package android.app.admin { public final class FactoryResetProtectionPolicy implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts(); - method public boolean isFactoryResetProtectionDisabled(); + method public boolean isFactoryResetProtectionEnabled(); method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int); field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR; } @@ -7247,7 +7251,7 @@ package android.app.admin { ctor public FactoryResetProtectionPolicy.Builder(); method @NonNull public android.app.admin.FactoryResetProtectionPolicy build(); method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>); - method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean); + method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionEnabled(boolean); } public class FreezePeriod { @@ -10141,7 +10145,6 @@ package android.content { field public static final String ALARM_SERVICE = "alarm"; field public static final String APPWIDGET_SERVICE = "appwidget"; field public static final String APP_OPS_SERVICE = "appops"; - field public static final String APP_SEARCH_SERVICE = "app_search"; field public static final String AUDIO_SERVICE = "audio"; field public static final String BATTERY_SERVICE = "batterymanager"; field public static final int BIND_ABOVE_CLIENT = 8; // 0x8 @@ -12068,7 +12071,7 @@ package android.content.pm { field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup"; field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice"; field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir"; - field public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub"; + field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin"; field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded"; field public static final String FEATURE_ETHERNET = "android.hardware.ethernet"; @@ -27041,7 +27044,7 @@ package android.media { method @NonNull public String getId(); method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes(); method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes(); - method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferrableRoutes(); + method @NonNull public java.util.List<android.media.MediaRoute2Info> getTransferableRoutes(); method public int getVolume(); method public int getVolumeHandling(); method public int getVolumeMax(); @@ -27421,7 +27424,7 @@ package android.media { public final class RouteDiscoveryPreference implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<java.lang.String> getPreferredFeatures(); - method public boolean isActiveScan(); + method public boolean shouldPerformActiveScan(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR; } @@ -27430,8 +27433,8 @@ package android.media { ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean); ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference); method @NonNull public android.media.RouteDiscoveryPreference build(); - method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean); method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean); } public final class RoutingSessionInfo implements android.os.Parcelable { @@ -27442,7 +27445,7 @@ package android.media { method @NonNull public String getId(); method @NonNull public java.util.List<java.lang.String> getSelectableRoutes(); method @NonNull public java.util.List<java.lang.String> getSelectedRoutes(); - method @NonNull public java.util.List<java.lang.String> getTransferrableRoutes(); + method @NonNull public java.util.List<java.lang.String> getTransferableRoutes(); method public int getVolume(); method public int getVolumeHandling(); method public int getVolumeMax(); @@ -27456,16 +27459,16 @@ package android.media { method @NonNull public android.media.RoutingSessionInfo.Builder addDeselectableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder addSelectableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder addSelectedRoute(@NonNull String); - method @NonNull public android.media.RoutingSessionInfo.Builder addTransferrableRoute(@NonNull String); + method @NonNull public android.media.RoutingSessionInfo.Builder addTransferableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo build(); method @NonNull public android.media.RoutingSessionInfo.Builder clearDeselectableRoutes(); method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectableRoutes(); method @NonNull public android.media.RoutingSessionInfo.Builder clearSelectedRoutes(); - method @NonNull public android.media.RoutingSessionInfo.Builder clearTransferrableRoutes(); + method @NonNull public android.media.RoutingSessionInfo.Builder clearTransferableRoutes(); method @NonNull public android.media.RoutingSessionInfo.Builder removeDeselectableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder removeSelectedRoute(@NonNull String); - method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferrableRoute(@NonNull String); + method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String); method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle); method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int); method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int); @@ -37311,6 +37314,7 @@ package android.os.storage { method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener); method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback); + field @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public static final String ACTION_CLEAR_APP_CACHE = "android.os.storage.action.CLEAR_APP_CACHE"; field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE"; field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES"; field public static final String EXTRA_UUID = "android.os.storage.extra.UUID"; diff --git a/api/system-current.txt b/api/system-current.txt index 0fd8a20600b0..8725538f1fd8 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1809,7 +1809,7 @@ package android.content { field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry"; field public static final String TETHERING_SERVICE = "tethering"; field public static final String VR_SERVICE = "vrmanager"; - field public static final String WIFI_COND_SERVICE = "wificond"; + field public static final String WIFI_NL80211_SERVICE = "wifinl80211"; field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager"; field public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; } @@ -2217,6 +2217,7 @@ package android.content.pm { field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000 field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 + field public static final int FLAG_PERMISSION_AUTO_REVOKED = 1048576; // 0x100000 field public static final int FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED = 131072; // 0x20000 field public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 262144; // 0x40000 field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20 @@ -2302,7 +2303,7 @@ package android.content.pm { method public void onPermissionsChanged(int); } - @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { + @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKE_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags { } public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { @@ -8329,21 +8330,21 @@ package android.net.wifi.wificond { field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.RadioChainInfo> CREATOR; } - public class WifiCondManager { + public class WifiNl80211Manager { method public void abortScan(@NonNull String); method public void enableVerboseLogging(boolean); method @NonNull public int[] getChannelsMhzForBand(int); method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int); - method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String); - method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); - method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback); - method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback); + method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); + method @Nullable public static android.net.wifi.wificond.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); + method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback); + method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.SendMgmtFrameCallback); method public void setOnServiceDeadCallback(@NonNull Runnable); - method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback); + method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiNl80211Manager.ScanEventCallback); method public boolean setupInterfaceForSoftApMode(@NonNull String); - method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String); - method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.PnoScanRequestCallback); + method @Nullable public android.net.wifi.wificond.WifiNl80211Manager.SignalPollResult signalPoll(@NonNull String); + method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiNl80211Manager.PnoScanRequestCallback); method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>); method public boolean stopPnoScan(@NonNull String); method public boolean tearDownClientInterface(@NonNull String); @@ -8358,43 +8359,43 @@ package android.net.wifi.wificond { field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1 } - public static class WifiCondManager.OemSecurityType { - ctor public WifiCondManager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int); + public static class WifiNl80211Manager.OemSecurityType { + ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int); field public final int groupCipher; field @NonNull public final java.util.List<java.lang.Integer> keyManagement; field @NonNull public final java.util.List<java.lang.Integer> pairwiseCipher; field public final int protocol; } - public static interface WifiCondManager.PnoScanRequestCallback { + public static interface WifiNl80211Manager.PnoScanRequestCallback { method public void onPnoRequestFailed(); method public void onPnoRequestSucceeded(); } - public static interface WifiCondManager.ScanEventCallback { + public static interface WifiNl80211Manager.ScanEventCallback { method public void onScanFailed(); method public void onScanResultReady(); } - public static interface WifiCondManager.SendMgmtFrameCallback { + public static interface WifiNl80211Manager.SendMgmtFrameCallback { method public void onAck(int); method public void onFailure(int); } - public static class WifiCondManager.SignalPollResult { + public static class WifiNl80211Manager.SignalPollResult { field public final int associationFrequencyMHz; field public final int currentRssiDbm; field public final int rxBitrateMbps; field public final int txBitrateMbps; } - public static interface WifiCondManager.SoftApCallback { + public static interface WifiNl80211Manager.SoftApCallback { method public void onConnectedClientsChanged(@NonNull android.net.wifi.wificond.NativeWifiClient, boolean); method public void onFailure(); method public void onSoftApChannelSwitched(int, int); } - public static class WifiCondManager.TxPacketCounters { + public static class WifiNl80211Manager.TxPacketCounters { field public final int txPacketFailed; field public final int txPacketSucceeded; } diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt index 0caee6bebbda..dfb0d7460ca4 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -234,7 +234,7 @@ OnNameExpected: android.content.ContentProvider#checkUriPermission(android.net.U If implemented by developer, should follow the on<Something> style; otherwise consider marking final -PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback): +PairedRegistration: android.net.wifi.wificond.WifiNl80211Manager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiNl80211Manager.SoftApCallback): diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 956fd29205cb..1bcf44e03c09 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -120,13 +120,14 @@ cc_defaults { "libstatslog", "libstatsmetadata", "libsysutils", + // TODO(b/145923087): move to shared when statsd is moved to the apex + "libstatssocket", "libutils", ], shared_libs: [ "libbinder", "libincident", "liblog", - "libstatssocket", "statsd-aidl-cpp", ], } diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 03f97d80824d..23a4437910f7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -381,7 +381,7 @@ message Atom { UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"]; NotificationReported notification_reported = 244 [(module) = "framework"]; NotificationPanelReported notification_panel_reported = 245; - NotificationChannelModified notification_panel_modified = 246; + NotificationChannelModified notification_channel_modified = 246; IntegrityCheckResultReported integrity_check_result_reported = 247 [(module) = "framework"]; IntegrityRulesPushed integrity_rules_pushed = 248 [(module) = "framework"]; CellBroadcastMessageReported cb_message_reported = diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 1bac19ed2c5d..4899b4a5247c 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -690,14 +690,16 @@ void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) { if (name[0] == '.') continue; FileName output; + string file_name; if (parseTimestampOnly) { + file_name = StringPrintf("%s/%s", path, name); output.mTimestampSec = StrToInt64(strtok(name, "_")); output.mIsHistory = false; } else { parseFileName(name, &output); + file_name = output.getFullFileName(path); } if (output.mTimestampSec == -1) continue; - string file_name = output.getFullFileName(path); // Check for timestamp and delete if it's too old. long fileAge = nowSec - output.mTimestampSec; diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index ace13513e39d..25729abf9e05 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -18,6 +18,7 @@ package android.accessibilityservice; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; @@ -25,6 +26,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT; @@ -32,6 +34,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; @@ -83,9 +86,11 @@ public final class AccessibilityGestureEvent implements Parcelable { @IntDef(prefix = { "GESTURE_" }, value = { GESTURE_2_FINGER_SINGLE_TAP, GESTURE_2_FINGER_DOUBLE_TAP, + GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, GESTURE_2_FINGER_TRIPLE_TAP, GESTURE_3_FINGER_SINGLE_TAP, GESTURE_3_FINGER_DOUBLE_TAP, + GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, GESTURE_3_FINGER_TRIPLE_TAP, GESTURE_DOUBLE_TAP, GESTURE_DOUBLE_TAP_AND_HOLD, @@ -114,6 +119,7 @@ public final class AccessibilityGestureEvent implements Parcelable { GESTURE_3_FINGER_SWIPE_RIGHT, GESTURE_3_FINGER_SWIPE_UP, GESTURE_4_FINGER_DOUBLE_TAP, + GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, GESTURE_4_FINGER_SINGLE_TAP, GESTURE_4_FINGER_SWIPE_DOWN, GESTURE_4_FINGER_SWIPE_LEFT, @@ -175,12 +181,18 @@ public final class AccessibilityGestureEvent implements Parcelable { switch (eventType) { case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP"; case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP"; + case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD: + return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD"; case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP"; case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP"; case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP"; + case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD: + return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD"; case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP"; case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP"; case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP"; + case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD: + return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD"; case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP"; case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP"; case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD"; diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b65f68e177ca..b7a35f7548c7 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -29,6 +29,7 @@ import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; import android.graphics.ColorSpace; +import android.graphics.ParcelableColorSpace; import android.graphics.Region; import android.hardware.HardwareBuffer; import android.os.Binder; @@ -39,6 +40,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.SystemClock; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -411,6 +413,15 @@ public abstract class AccessibilityService extends Service { /** The user has performed a four-finger triple tap gesture on the touch screen. */ public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39; + /** The user has performed a two-finger double tap and hold gesture on the touch screen. */ + public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; + + /** The user has performed a three-finger double tap and hold gesture on the touch screen. */ + public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; + + /** The user has performed a two-finger double tap and hold gesture on the touch screen. */ + public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; + /** * The {@link Intent} that must be declared as handled by the service. */ @@ -591,8 +602,12 @@ public abstract class AccessibilityService extends Service { "screenshot_hardwareBuffer"; /** @hide */ - public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID = - "screenshot_colorSpaceId"; + public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE = + "screenshot_colorSpace"; + + /** @hide */ + public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP = + "screenshot_timestamp"; /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. @@ -1911,6 +1926,8 @@ public abstract class AccessibilityService extends Service { * default display. * @param executor Executor on which to run the callback. * @param callback The callback invoked when the taking screenshot is done. + * The {@link AccessibilityService.ScreenshotResult} will be null for an + * invalid display. * * @return {@code true} if the taking screenshot accepted, {@code false} if not. */ @@ -1932,14 +1949,11 @@ public abstract class AccessibilityService extends Service { } final HardwareBuffer hardwareBuffer = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER); - final int colorSpaceId = - result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID); - ColorSpace colorSpace = null; - if (colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length) { - colorSpace = ColorSpace.get(ColorSpace.Named.values()[colorSpaceId]); - } + final ParcelableColorSpace colorSpace = + result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE); ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer, - colorSpace, System.currentTimeMillis()); + colorSpace.getColorSpace(), + result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP)); sendScreenshotResult(executor, callback, screenshot); })); } catch (RemoteException re) { @@ -2352,41 +2366,38 @@ public abstract class AccessibilityService extends Service { } /** - * Class including hardwareBuffer, colorSpace, and timestamp to be the result for + * Can be used to construct a bitmap of the screenshot or any other operations for * {@link AccessibilityService#takeScreenshot} API. - * <p> - * <strong>Note:</strong> colorSpace would be null if the name of this colorSpace isn't at - * {@link ColorSpace.Named}. - * </p> */ public static final class ScreenshotResult { private final @NonNull HardwareBuffer mHardwareBuffer; - private final @Nullable ColorSpace mColorSpace; + private final @NonNull ColorSpace mColorSpace; private final long mTimestamp; private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer, - @Nullable ColorSpace colorSpace, long timestamp) { + @NonNull ColorSpace colorSpace, long timestamp) { Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null"); + Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null"); mHardwareBuffer = hardwareBuffer; mColorSpace = colorSpace; mTimestamp = timestamp; } /** - * Gets the colorSpace identifying a specific organization of colors of the screenshot. + * Gets the {@link ColorSpace} identifying a specific organization of colors of the + * screenshot. * - * @return the colorSpace or {@code null} if the name of colorSpace isn't at - * {@link ColorSpace.Named} + * @return the color space */ - @Nullable + @NonNull public ColorSpace getColorSpace() { return mColorSpace; } /** - * Gets the hardwareBuffer representing a memory buffer of the screenshot. + * Gets the {@link HardwareBuffer} representing a memory buffer of the screenshot. * - * @return the hardwareBuffer + * @return the hardware buffer */ @NonNull public HardwareBuffer getHardwareBuffer() { @@ -2396,7 +2407,8 @@ public abstract class AccessibilityService extends Service { /** * Gets the timestamp of taking the screenshot. * - * @return the timestamp from {@link System#currentTimeMillis()} + * @return milliseconds of non-sleep uptime before screenshot since boot and it's from + * {@link SystemClock#uptimeMillis()} */ public long getTimestamp() { return mTimestamp; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 82c7635fa5e3..c1e21959a667 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -551,11 +551,6 @@ public class AccessibilityServiceInfo implements Parcelable { private int mHtmlDescriptionRes; /** - * Non localized html description of the accessibility service. - */ - private String mNonLocalizedHtmlDescription; - - /** * Creates a new instance. */ public AccessibilityServiceInfo() { @@ -683,10 +678,6 @@ public class AccessibilityServiceInfo implements Parcelable { com.android.internal.R.styleable.AccessibilityService_htmlDescription); if (peekedValue != null) { mHtmlDescriptionRes = peekedValue.resourceId; - final CharSequence nonLocalizedHtmlDescription = peekedValue.coerceToString(); - if (nonLocalizedHtmlDescription != null) { - mNonLocalizedHtmlDescription = nonLocalizedHtmlDescription.toString().trim(); - } } asAttributes.recycle(); } catch (NameNotFoundException e) { @@ -919,7 +910,7 @@ public class AccessibilityServiceInfo implements Parcelable { @Nullable public String loadHtmlDescription(@NonNull PackageManager packageManager) { if (mHtmlDescriptionRes == 0) { - return mNonLocalizedHtmlDescription; + return null; } final ServiceInfo serviceInfo = mResolveInfo.serviceInfo; @@ -1017,7 +1008,6 @@ public class AccessibilityServiceInfo implements Parcelable { parcel.writeInt(mAnimatedImageRes); parcel.writeInt(mHtmlDescriptionRes); parcel.writeString(mNonLocalizedDescription); - parcel.writeString(mNonLocalizedHtmlDescription); } private void initFromParcel(Parcel parcel) { @@ -1039,7 +1029,6 @@ public class AccessibilityServiceInfo implements Parcelable { mAnimatedImageRes = parcel.readInt(); mHtmlDescriptionRes = parcel.readInt(); mNonLocalizedDescription = parcel.readString(); - mNonLocalizedHtmlDescription = parcel.readString(); } @Override diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 367c2f235f5f..1de68ba56c6a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -7824,9 +7824,7 @@ public class AppOpsManager { * @hide */ public static boolean isCollectingNotedAppOps() { - synchronized (sLock) { - return sNotedAppOpsCollector != null; - } + return sNotedAppOpsCollector != null; } /** diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 9aa6b870792d..526c0b3f87ee 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -162,6 +162,7 @@ interface INotificationManager void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment); void applyAdjustmentsFromAssistant(in INotificationListener token, in List<Adjustment> adjustments); void unsnoozeNotificationFromAssistant(in INotificationListener token, String key); + void unsnoozeNotificationFromSystemListener(in INotificationListener token, String key); ComponentName getEffectsSuppressor(); boolean matchesCallFilter(in Bundle extras); diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 06288c0794b0..37bdda0b0393 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -204,4 +204,12 @@ oneway interface ITaskStackListener { * @param frozen if true, Recents Tasks list is currently frozen, false otherwise */ void onRecentTaskListFrozenChanged(boolean frozen); + + /** + * Called when a task gets or loses focus. + * + * @param taskId id of the task. + * @param {@code true} if the task got focus, {@code false} if it lost it. + */ + void onTaskFocusChanged(int taskId, boolean focused); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 8b07418668ba..9b7306089dca 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -132,7 +132,7 @@ import android.net.lowpan.LowpanManager; import android.net.nsd.INsdManager; import android.net.nsd.NsdManager; import android.net.wifi.WifiFrameworkInitializer; -import android.net.wifi.wificond.WifiCondManager; +import android.net.wifi.wificond.WifiNl80211Manager; import android.nfc.NfcManager; import android.os.BatteryManager; import android.os.BatteryStats; @@ -761,11 +761,11 @@ public final class SystemServiceRegistry { return new EthernetManager(ctx.getOuterContext(), service); }}); - registerService(Context.WIFI_COND_SERVICE, WifiCondManager.class, - new CachedServiceFetcher<WifiCondManager>() { + registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class, + new CachedServiceFetcher<WifiNl80211Manager>() { @Override - public WifiCondManager createService(ContextImpl ctx) { - return new WifiCondManager(ctx.getOuterContext()); + public WifiNl80211Manager createService(ContextImpl ctx) { + return new WifiNl80211Manager(ctx.getOuterContext()); } }); diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 343b3869d354..da0aadb3ea48 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -199,4 +199,8 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override public void onRecentTaskListFrozenChanged(boolean frozen) { } + + @Override + public void onTaskFocusChanged(int taskId, boolean focused) { + } } diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java index ed7477936f9c..954db0459f99 100644 --- a/core/java/android/app/admin/FactoryResetProtectionPolicy.java +++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java @@ -53,17 +53,17 @@ public final class FactoryResetProtectionPolicy implements Parcelable { private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT = "factory_reset_protection_account"; - private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED = - "factory_reset_protection_disabled"; + private static final String KEY_FACTORY_RESET_PROTECTION_ENABLED = + "factory_reset_protection_enabled"; private static final String ATTR_VALUE = "value"; private final List<String> mFactoryResetProtectionAccounts; - private final boolean mFactoryResetProtectionDisabled; + private final boolean mFactoryResetProtectionEnabled; private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts, - boolean factoryResetProtectionDisabled) { + boolean factoryResetProtectionEnabled) { mFactoryResetProtectionAccounts = factoryResetProtectionAccounts; - mFactoryResetProtectionDisabled = factoryResetProtectionDisabled; + mFactoryResetProtectionEnabled = factoryResetProtectionEnabled; } /** @@ -74,10 +74,10 @@ public final class FactoryResetProtectionPolicy implements Parcelable { } /** - * Return whether factory reset protection for the device is disabled or not. + * Return whether factory reset protection for the device is enabled or not. */ - public boolean isFactoryResetProtectionDisabled() { - return mFactoryResetProtectionDisabled; + public boolean isFactoryResetProtectionEnabled() { + return mFactoryResetProtectionEnabled; } /** @@ -85,12 +85,13 @@ public final class FactoryResetProtectionPolicy implements Parcelable { */ public static class Builder { private List<String> mFactoryResetProtectionAccounts; - private boolean mFactoryResetProtectionDisabled; + private boolean mFactoryResetProtectionEnabled; /** * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}. */ public Builder() { + mFactoryResetProtectionEnabled = true; }; /** @@ -113,18 +114,19 @@ public final class FactoryResetProtectionPolicy implements Parcelable { } /** - * Sets whether factory reset protection is disabled or not. + * Sets whether factory reset protection is enabled or not. * <p> * Once disabled, factory reset protection will not kick in all together when the device * goes through untrusted factory reset. This applies to both the consumer unlock flow and - * the admin account overrides via {@link #setFactoryResetProtectionAccounts} + * the admin account overrides via {@link #setFactoryResetProtectionAccounts}. By default, + * factory reset protection is enabled. * - * @param factoryResetProtectionDisabled Whether the policy is disabled or not. + * @param factoryResetProtectionEnabled Whether the policy is enabled or not. * @return the same Builder instance. */ @NonNull - public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) { - mFactoryResetProtectionDisabled = factoryResetProtectionDisabled; + public Builder setFactoryResetProtectionEnabled(boolean factoryResetProtectionEnabled) { + mFactoryResetProtectionEnabled = factoryResetProtectionEnabled; return this; } @@ -136,7 +138,7 @@ public final class FactoryResetProtectionPolicy implements Parcelable { @NonNull public FactoryResetProtectionPolicy build() { return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts, - mFactoryResetProtectionDisabled); + mFactoryResetProtectionEnabled); } } @@ -144,7 +146,7 @@ public final class FactoryResetProtectionPolicy implements Parcelable { public String toString() { return "FactoryResetProtectionPolicy{" + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts - + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled + + ", mFactoryResetProtectionEnabled=" + mFactoryResetProtectionEnabled + '}'; } @@ -155,7 +157,7 @@ public final class FactoryResetProtectionPolicy implements Parcelable { for (String account: mFactoryResetProtectionAccounts) { dest.writeString(account); } - dest.writeBoolean(mFactoryResetProtectionDisabled); + dest.writeBoolean(mFactoryResetProtectionEnabled); } @Override @@ -173,10 +175,10 @@ public final class FactoryResetProtectionPolicy implements Parcelable { for (int i = 0; i < accountsCount; i++) { factoryResetProtectionAccounts.add(in.readString()); } - boolean factoryResetProtectionDisabled = in.readBoolean(); + boolean factoryResetProtectionEnabled = in.readBoolean(); return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts, - factoryResetProtectionDisabled); + factoryResetProtectionEnabled); } @Override @@ -195,8 +197,8 @@ public final class FactoryResetProtectionPolicy implements Parcelable { @Nullable public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) { try { - boolean factoryResetProtectionDisabled = Boolean.parseBoolean( - parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED)); + boolean factoryResetProtectionEnabled = Boolean.parseBoolean( + parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_ENABLED)); List<String> factoryResetProtectionAccounts = new ArrayList<>(); int outerDepth = parser.getDepth(); @@ -214,7 +216,7 @@ public final class FactoryResetProtectionPolicy implements Parcelable { } return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts, - factoryResetProtectionDisabled); + factoryResetProtectionEnabled); } catch (XmlPullParserException | IOException e) { Log.w(LOG_TAG, "Reading from xml failed", e); } @@ -225,8 +227,8 @@ public final class FactoryResetProtectionPolicy implements Parcelable { * @hide */ public void writeToXml(@NonNull XmlSerializer out) throws IOException { - out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED, - Boolean.toString(mFactoryResetProtectionDisabled)); + out.attribute(null, KEY_FACTORY_RESET_PROTECTION_ENABLED, + Boolean.toString(mFactoryResetProtectionEnabled)); for (String account : mFactoryResetProtectionAccounts) { out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT); out.attribute(null, ATTR_VALUE, account); diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index ab71e73fd58c..0f999ad68a62 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -41,6 +41,43 @@ public final class UsageEvents implements Parcelable { /** @hide */ public static final String INSTANT_APP_CLASS_NAME = "android.instant_class"; + /** @hide */ + public static final String OBFUSCATED_NOTIFICATION_CHANNEL_ID = "unknown_channel_id"; + + /** + * Flag: indicates to not obfuscate or hide any usage event data when being queried. + * @hide + */ + public static final int SHOW_ALL_EVENT_DATA = 0x00000000; + + /** + * Flag: indicates to obfuscate package and class names for instant apps when querying usage + * events. + * @hide + */ + public static final int OBFUSCATE_INSTANT_APPS = 0x00000001; + + /** + * Flag: indicates to hide all {@link Event#SHORTCUT_INVOCATION} events when querying usage + * events. + * @hide + */ + public static final int HIDE_SHORTCUT_EVENTS = 0x00000002; + + /** + * Flag: indicates to obfuscate the notification channel id for all notification events, + * such as {@link Event#NOTIFICATION_SEEN} and {@link Event#NOTIFICATION_INTERRUPTION} events, + * when querying usage events. + * @hide + */ + public static final int OBFUSCATE_NOTIFICATION_EVENTS = 0x00000004; + + /** + * Flag: indicates to hide all {@link Event#LOCUS_ID_SET} events when querying usage events. + * @hide + */ + public static final int HIDE_LOCUS_EVENTS = 0x00000008; + /** * An event representing a state change for a component. */ @@ -627,6 +664,13 @@ public final class UsageEvents implements Parcelable { return ret; } + /** @hide */ + public Event getObfuscatedNotificationEvent() { + final Event ret = new Event(this); + ret.mNotificationChannelId = OBFUSCATED_NOTIFICATION_CHANNEL_ID; + return ret; + } + /** * Returns the locusId for this event if the event is of type {@link #LOCUS_ID_SET}, * otherwise it returns null. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 49f62f407806..ae12de027e6e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4064,16 +4064,16 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a - * {@link android.net.wifi.WifiCondManager} for handling management of the Wi-Fi control - * daemon. + * {@link android.net.wifi.wificond.WifiNl80211Manager} for handling management of the + * Wi-Fi nl802.11 daemon (wificond). * * @see #getSystemService(String) - * @see android.net.wifi.WifiCondManager + * @see android.net.wifi.wificond.WifiNl80211Manager * @hide */ @SystemApi @SuppressLint("ServiceName") - public static final String WIFI_COND_SERVICE = "wificond"; + public static final String WIFI_NL80211_SERVICE = "wifinl80211"; /** * Use with {@link #getSystemService(String)} to retrieve a {@link @@ -5098,10 +5098,11 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an - * AppSearchManager for indexing and querying app data managed - * by the system. + * {@link android.app.appsearch.AppSearchManager} for + * indexing and querying app data managed by the system. * * @see #getSystemService(String) + * @hide */ public static final String APP_SEARCH_SERVICE = "app_search"; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index d251ba9519db..9d1c677f35c6 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -702,6 +702,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_ODM = 1 << 30; + /** + * Value for {@link #privateFlags}: If {@code true} this app allows heap tagging. + * {@link com.android.server.am.ProcessList#NATIVE_HEAP_POINTER_TAGGING} + * @hide + */ + public static final int PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING = 1 << 31; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = { PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE, @@ -733,6 +740,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE, PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE, PRIVATE_FLAG_ODM, + PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlags {} @@ -1878,6 +1886,15 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0; } + /** + * If {@code true} this app allows heap pointer tagging. + * + * @hide + */ + public boolean allowsNativeHeapPointerTagging() { + return (privateFlags & PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING) != 0; + } + private boolean isAllowedToUseHiddenApis() { if (isSignedWithPlatformKey()) { return true; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c78d30dd9133..7b484b7c2c3d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -38,7 +38,7 @@ import android.app.PackageInstallObserver; import android.app.admin.DevicePolicyManager; import android.app.usage.StorageStatsManager; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -1988,10 +1988,11 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and - * {@link #hasSystemFeature}: The device supports a Context Hub. + * {@link #hasSystemFeature}: The device supports a Context Hub, used to expose the + * functionalities in {@link android.hardware.location.ContextHubManager}. */ @SdkConstant(SdkConstantType.FEATURE) - public static final String FEATURE_CONTEXTHUB = "android.hardware.context_hub"; + public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; /** {@hide} */ @SdkConstant(SdkConstantType.FEATURE) @@ -3385,6 +3386,14 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_AUTO_REVOKE_USER_SET = 1 << 18; /** + * Permission flag: Whether permission was revoked by auto-revoke. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 20; + + /** * Permission flags: Reserved for use by the permission controller. * * @hide @@ -3437,7 +3446,8 @@ public abstract class PackageManager { | FLAG_PERMISSION_REVOKED_COMPAT | FLAG_PERMISSION_ONE_TIME | FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED - | FLAG_PERMISSION_AUTO_REVOKE_USER_SET; + | FLAG_PERMISSION_AUTO_REVOKE_USER_SET + | FLAG_PERMISSION_AUTO_REVOKED; /** * Injected activity in app that forwards user to setting activity of that app. @@ -3581,7 +3591,7 @@ public abstract class PackageManager { * @hide */ @ChangeId - @Disabled + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) public static final long FILTER_APPLICATION_QUERY = 135549675L; /** {@hide} */ @@ -4262,7 +4272,8 @@ public abstract class PackageManager { FLAG_PERMISSION_REVOKED_COMPAT, FLAG_PERMISSION_ONE_TIME, FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED, - FLAG_PERMISSION_AUTO_REVOKE_USER_SET + FLAG_PERMISSION_AUTO_REVOKE_USER_SET, + FLAG_PERMISSION_AUTO_REVOKED }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionFlags {} @@ -7401,6 +7412,7 @@ public abstract class PackageManager { case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME"; case FLAG_PERMISSION_AUTO_REVOKE_IF_UNUSED: return "AUTO_REVOKE_IF_UNUSED"; case FLAG_PERMISSION_AUTO_REVOKE_USER_SET: return "AUTO_REVOKE_USER_SET"; + case FLAG_PERMISSION_AUTO_REVOKED: return "AUTO_REVOKED"; default: return Integer.toString(flag); } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 637e64d3d2c2..da44f70178de 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3710,6 +3710,11 @@ public class PackageParser { ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE; } + if (sa.getBoolean( + R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true)) { + ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING; + } + ai.maxAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0); ai.minAspectRatio = sa.getFloat(R.styleable.AndroidManifestApplication_minAspectRatio, 0); diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index 548d82a6ab76..a2671136ff7b 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -2098,6 +2098,9 @@ public class ApkParseUtils { R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, parsingPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q)); + parsingPackage.setAllowNativeHeapPointerTagging(sa.getBoolean( + R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, true)); + parsingPackage .setMaxAspectRatio( sa.getFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, 0)) diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index 0df950006f43..778d7b8b26b6 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -1509,6 +1509,16 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public PackageImpl setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging) { + this.privateFlags = allowNativeHeapPointerTagging + ? this.privateFlags | ApplicationInfo + .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING + : this.privateFlags & ~ApplicationInfo + .PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING; + return this; + } + + @Override public PackageImpl setUsesNonSdkApi(boolean usesNonSdkApi) { this.privateFlags = usesNonSdkApi ? this.privateFlags | ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index a2fe064b66c3..954d65c75730 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -191,6 +191,8 @@ public interface ParsingPackage extends AndroidPackage { ParsingPackage setRequestLegacyExternalStorage(boolean requestLegacyExternalStorage); + ParsingPackage setAllowNativeHeapPointerTagging(boolean allowNativeHeapPointerTagging); + ParsingPackage setRestoreAnyVersion(boolean restoreAnyVersion); ParsingPackage setSplitHasCode(int splitIndex, boolean splitHasCode); diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index ef28e6c6db2a..ac36188ee61a 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -75,6 +75,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan /** * @hide */ + public static final String KEY_DEVICE_CREDENTIAL_TITLE = "device_credential_title"; + /** + * @hide + */ + public static final String KEY_DEVICE_CREDENTIAL_SUBTITLE = "device_credential_subtitle"; + /** + * @hide + */ + public static final String KEY_DEVICE_CREDENTIAL_DESCRIPTION = "device_credential_description"; + /** + * @hide + */ public static final String KEY_NEGATIVE_TEXT = "negative_text"; /** * @hide @@ -221,6 +233,30 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Sets an optional title, subtitle, and/or description that will override other text when + * the user is authenticating with PIN/pattern/password. Currently for internal use only. + * @return This builder. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + @NonNull + public Builder setTextForDeviceCredential( + @Nullable CharSequence title, + @Nullable CharSequence subtitle, + @Nullable CharSequence description) { + if (title != null) { + mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_TITLE, title); + } + if (subtitle != null) { + mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_SUBTITLE, subtitle); + } + if (description != null) { + mBundle.putCharSequence(KEY_DEVICE_CREDENTIAL_DESCRIPTION, description); + } + return this; + } + + /** * Required: Sets the text, executor, and click listener for the negative button on the * prompt. This is typically a cancel button, but may be also used to show an alternative * method for authentication, such as a screen that asks for a backup password. diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index db16d24e0af1..1ed791d66f74 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -54,7 +54,7 @@ import java.util.concurrent.Executor; */ @SystemApi @SystemService(Context.CONTEXTHUB_SERVICE) -@RequiresFeature(PackageManager.FEATURE_CONTEXTHUB) +@RequiresFeature(PackageManager.FEATURE_CONTEXT_HUB) public final class ContextHubManager { private static final String TAG = "ContextHubManager"; diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS new file mode 100644 index 000000000000..444719701df2 --- /dev/null +++ b/core/java/android/inputmethodservice/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include ../../../../services/core/java/com/android/server/inputmethod/OWNERS diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java index 6450a67572bf..6516917afd9d 100644 --- a/core/java/android/os/incremental/V4Signature.java +++ b/core/java/android/os/incremental/V4Signature.java @@ -16,6 +16,7 @@ package android.os.incremental; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -47,6 +48,15 @@ public class V4Signature { } /** + * Construct a V4Signature from .idsig file. + */ + public static V4Signature readFrom(byte[] bytes) throws IOException { + try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) { + return readFrom(stream); + } + } + + /** * Store the V4Signature to a byte-array. */ public byte[] toByteArray() { diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index bb1dafc50484..8d04df0560f5 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -215,6 +215,20 @@ public class StorageManager { public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE"; /** + * Activity Action: Allows the user to free up space by clearing app external cache directories. + * The intent doesn't automatically clear cache, but shows a dialog and lets the user decide. + * <p> + * This intent should be launched using + * {@link Activity#startActivityForResult(Intent, int)} so that the user + * knows which app is requesting to clear cache. The returned result will + * be {@link Activity#RESULT_OK} if the activity was launched and the user accepted to clear + * cache, or {@link Activity#RESULT_CANCELED} otherwise. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CLEAR_APP_CACHE = "android.os.storage.action.CLEAR_APP_CACHE"; + + /** * Extra {@link UUID} used to indicate the storage volume where an * application is interested in allocating or managing disk space. * diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index afeb6c391009..44e7ae6c13c5 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -79,6 +79,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_tether_all_in_one", "false"); DEFAULT_FLAGS.put(SETTINGS_SCHEDULES_FLAG, "false"); + DEFAULT_FLAGS.put("settings_contextual_home2", "false"); } /** diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index bf848196454d..680a8789a6b8 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -24,6 +24,7 @@ import android.graphics.PixelFormat; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.view.accessibility.IAccessibilityEmbeddedConnection; /** * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy @@ -40,6 +41,7 @@ public class SurfaceControlViewHost { private WindowlessWindowManager mWm; private SurfaceControl mSurfaceControl; + private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; /** * Package encapsulating a Surface hierarchy which contains interactive view @@ -49,15 +51,18 @@ public class SurfaceControlViewHost { */ public static final class SurfacePackage implements Parcelable { private final SurfaceControl mSurfaceControl; - // TODO: Accessibility ID goes here + private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; - SurfacePackage(SurfaceControl sc) { + SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection) { mSurfaceControl = sc; + mAccessibilityEmbeddedConnection = connection; } private SurfacePackage(Parcel in) { mSurfaceControl = new SurfaceControl(); mSurfaceControl.readFromParcel(in); + mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface( + in.readStrongBinder()); } /** @@ -69,6 +74,16 @@ public class SurfaceControlViewHost { return mSurfaceControl; } + /** + * Gets an accessibility embedded connection interface for this SurfaceControlViewHost. + * + * @return {@link IAccessibilityEmbeddedConnection} interface. + * @hide + */ + public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() { + return mAccessibilityEmbeddedConnection; + } + @Override public int describeContents() { return 0; @@ -77,6 +92,7 @@ public class SurfaceControlViewHost { @Override public void writeToParcel(@NonNull Parcel out, int flags) { mSurfaceControl.writeToParcel(out, flags); + out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder()); } public static final @NonNull Creator<SurfacePackage> CREATOR @@ -95,6 +111,7 @@ public class SurfaceControlViewHost { @NonNull WindowlessWindowManager wwm) { mWm = wwm; mViewRoot = new ViewRootImpl(c, d, mWm); + mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } /** @@ -118,6 +135,7 @@ public class SurfaceControlViewHost { mWm = new WindowlessWindowManager(context.getResources().getConfiguration(), mSurfaceControl, hostToken); mViewRoot = new ViewRootImpl(context, display, mWm); + mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } /** @@ -128,8 +146,8 @@ public class SurfaceControlViewHost { * are linked. */ public @Nullable SurfacePackage getSurfacePackage() { - if (mSurfaceControl != null) { - return new SurfacePackage(mSurfaceControl); + if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) { + return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection); } else { return null; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index d5ed36b57c02..47ffd3e2714c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -28,6 +28,7 @@ import android.content.res.CompatibilityInfo.Translator; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; @@ -38,12 +39,14 @@ import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; import android.view.SurfaceControlViewHost; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.IAccessibilityEmbeddedConnection; import com.android.internal.view.SurfaceCallbackHelper; @@ -203,8 +206,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); private int mParentSurfaceGenerationId; - // The token of embedded windowless view hierarchy. - private IBinder mEmbeddedViewHierarchy; + private RemoteAccessibilityEmbeddedConnection mRemoteAccessibilityEmbeddedConnection; + + private final Matrix mScreenMatrixForEmbeddedHierarchy = new Matrix(); + private final Matrix mTmpMatrix = new Matrix(); + private final float[] mMatrixValues = new float[9]; + SurfaceControlViewHost.SurfacePackage mSurfacePackage; public SurfaceView(Context context) { @@ -923,6 +930,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } mTmpTransaction.apply(); + updateScreenMatrixForEmbeddedHierarchy(); if (sizeChanged || creating) { redrawNeeded = true; @@ -1510,6 +1518,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void surfaceDestroyed() { setWindowStopped(true); + setRemoteAccessibilityEmbeddedConnection(null, null); } /** @@ -1568,31 +1577,133 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void reparentSurfacePackage(SurfaceControl.Transaction t, SurfaceControlViewHost.SurfacePackage p) { - // TODO: Link accessibility IDs here. + initEmbeddedHierarchyForAccessibility(p); final SurfaceControl sc = p.getSurfaceControl(); t.reparent(sc, mSurfaceControl).show(sc); } - /** - * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view - * hierarchy. - * - * @param token IBinder token. - * @hide - */ - public void setEmbeddedViewHierarchy(IBinder token) { - mEmbeddedViewHierarchy = token; - } - /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); - if (mEmbeddedViewHierarchy == null) { + final RemoteAccessibilityEmbeddedConnection wrapper = + getRemoteAccessibilityEmbeddedConnection(); + if (wrapper == null) { return; } // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this // leashed child would return the root node in the embedded hierarchy - info.addChild(mEmbeddedViewHierarchy); + info.addChild(wrapper.getLeashToken()); + } + + private void initEmbeddedHierarchyForAccessibility(SurfaceControlViewHost.SurfacePackage p) { + final IAccessibilityEmbeddedConnection connection = p.getAccessibilityEmbeddedConnection(); + final RemoteAccessibilityEmbeddedConnection wrapper = + getRemoteAccessibilityEmbeddedConnection(); + + // Do nothing if package is embedding the same view hierarchy. + if (wrapper != null && wrapper.getConnection().equals(connection)) { + return; + } + + // If this SurfaceView embeds a different view hierarchy, unlink the previous one first. + setRemoteAccessibilityEmbeddedConnection(null, null); + + try { + final IBinder leashToken = connection.associateEmbeddedHierarchy( + getViewRootImpl().mLeashToken, getAccessibilityViewId()); + setRemoteAccessibilityEmbeddedConnection(connection, leashToken); + } catch (RemoteException e) { + Log.d(TAG, "Error while associateEmbeddedHierarchy " + e); + } + updateScreenMatrixForEmbeddedHierarchy(); + } + + private void setRemoteAccessibilityEmbeddedConnection( + IAccessibilityEmbeddedConnection connection, IBinder leashToken) { + try { + if (mRemoteAccessibilityEmbeddedConnection != null) { + mRemoteAccessibilityEmbeddedConnection.getConnection() + .disassociateEmbeddedHierarchy(); + mRemoteAccessibilityEmbeddedConnection.unlinkToDeath(); + mRemoteAccessibilityEmbeddedConnection = null; + } + if (connection != null && leashToken != null) { + mRemoteAccessibilityEmbeddedConnection = + new RemoteAccessibilityEmbeddedConnection(connection, leashToken); + mRemoteAccessibilityEmbeddedConnection.linkToDeath(); + } + } catch (RemoteException e) { + Log.d(TAG, "Error while setRemoteEmbeddedConnection " + e); + } + } + + private RemoteAccessibilityEmbeddedConnection getRemoteAccessibilityEmbeddedConnection() { + return mRemoteAccessibilityEmbeddedConnection; + } + + private void updateScreenMatrixForEmbeddedHierarchy() { + mTmpMatrix.reset(); + mTmpMatrix.setTranslate(mScreenRect.left, mScreenRect.top); + mTmpMatrix.postScale(mScreenRect.width() / (float) mSurfaceWidth, + mScreenRect.height() / (float) mSurfaceHeight); + + // If the screen matrix is identity or doesn't change, do nothing. + if (mTmpMatrix.isIdentity() || mTmpMatrix.equals(mScreenMatrixForEmbeddedHierarchy)) { + return; + } + + try { + final RemoteAccessibilityEmbeddedConnection wrapper = + getRemoteAccessibilityEmbeddedConnection(); + if (wrapper == null) { + return; + } + mTmpMatrix.getValues(mMatrixValues); + wrapper.getConnection().setScreenMatrix(mMatrixValues); + mScreenMatrixForEmbeddedHierarchy.set(mTmpMatrix); + } catch (RemoteException e) { + Log.d(TAG, "Error while setScreenMatrix " + e); + } + } + + /** + * Wrapper of accessibility embedded connection for embedded view hierarchy. + */ + private final class RemoteAccessibilityEmbeddedConnection implements IBinder.DeathRecipient { + private final IAccessibilityEmbeddedConnection mConnection; + private final IBinder mLeashToken; + + RemoteAccessibilityEmbeddedConnection(IAccessibilityEmbeddedConnection connection, + IBinder leashToken) { + mConnection = connection; + mLeashToken = leashToken; + } + + IAccessibilityEmbeddedConnection getConnection() { + return mConnection; + } + + IBinder getLeashToken() { + return mLeashToken; + } + + void linkToDeath() throws RemoteException { + mConnection.asBinder().linkToDeath(this, 0); + } + + void unlinkToDeath() { + mConnection.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + unlinkToDeath(); + runOnUiThread(() -> { + if (mRemoteAccessibilityEmbeddedConnection == this) { + mRemoteAccessibilityEmbeddedConnection = null; + } + }); + } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 857bc5058d60..b971a20d11c9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -655,7 +655,7 @@ public final class ViewRootImpl implements ViewParent, private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); - private IAccessibilityEmbeddedConnection mEmbeddedConnection; + private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; static final class SystemUiVisibilityInfo { int seq; @@ -9370,11 +9370,12 @@ public final class ViewRootImpl implements ViewParent, * Gets an accessibility embedded connection interface for this ViewRootImpl. * @hide */ - public IAccessibilityEmbeddedConnection getEmbeddedConnection() { - if (mEmbeddedConnection == null) { - mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this); + public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() { + if (mAccessibilityEmbeddedConnection == null) { + mAccessibilityEmbeddedConnection = new AccessibilityEmbeddedConnection( + ViewRootImpl.this); } - return mEmbeddedConnection; + return mAccessibilityEmbeddedConnection; } private class SendWindowContentChangedAccessibilityEvent implements Runnable { diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS new file mode 100644 index 000000000000..244cc30e089e --- /dev/null +++ b/core/java/android/view/inputmethod/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include ../../../../../services/core/java/com/android/server/inputmethod/OWNERS diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java index 8a1a0b5ef9b0..556b24c94b36 100644 --- a/core/java/android/webkit/UserPackage.java +++ b/core/java/android/webkit/UserPackage.java @@ -34,7 +34,7 @@ public class UserPackage { private final UserInfo mUserInfo; private final PackageInfo mPackageInfo; - public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.Q; + public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.R; public UserPackage(UserInfo user, PackageInfo packageInfo) { this.mUserInfo = user; diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 941af6ef1d7a..8790bbdcd8f7 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -47,7 +47,7 @@ public final class WebViewFactory { // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote. /** @hide */ private static final String CHROMIUM_WEBVIEW_FACTORY = - "com.android.webview.chromium.WebViewChromiumFactoryProviderForQ"; + "com.android.webview.chromium.WebViewChromiumFactoryProviderForR"; private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create"; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 46b8b771de87..12f245e87da2 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -5271,6 +5271,8 @@ public class Editor { if (opacity < 10 || opacity > 100) { opacity = 50; } + // Converts the opacity value from range {0..100} to {0..255}. + opacity = opacity * 255 / 100; } mDeltaHeight = deltaHeight; mDrawableOpacity = opacity; diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 78e85180f3d2..efcd54f623ee 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -61,6 +61,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private Set<Integer> mLoadedPages; private final UserHandle mPersonalProfileUserHandle; private final UserHandle mWorkProfileUserHandle; + private Injector mInjector; AbstractMultiProfilePagerAdapter(Context context, int currentPage, UserHandle personalProfileUserHandle, @@ -70,6 +71,33 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { mLoadedPages = new HashSet<>(); mPersonalProfileUserHandle = personalProfileUserHandle; mWorkProfileUserHandle = workProfileUserHandle; + UserManager userManager = context.getSystemService(UserManager.class); + mInjector = new Injector() { + @Override + public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, + int targetUserId) { + return AbstractMultiProfilePagerAdapter.this + .hasCrossProfileIntents(intents, sourceUserId, targetUserId); + } + + @Override + public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) { + return userManager.isQuietModeEnabled(workProfileUserHandle); + } + + @Override + public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) { + userManager.requestQuietModeEnabled(enabled, workProfileUserHandle); + } + }; + } + + /** + * Overrides the default {@link Injector} for testing purposes. + */ + @VisibleForTesting + public void setInjector(Injector injector) { + mInjector = injector; } void setOnProfileSelectedListener(OnProfileSelectedListener listener) { @@ -252,19 +280,18 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) { UserHandle listUserHandle = activeListAdapter.getUserHandle(); - UserManager userManager = mContext.getSystemService(UserManager.class); if (listUserHandle == mWorkProfileUserHandle - && userManager.isQuietModeEnabled(mWorkProfileUserHandle)) { + && mInjector.isQuietModeEnabled(mWorkProfileUserHandle)) { showEmptyState(activeListAdapter, R.drawable.ic_work_apps_off, R.string.resolver_turn_on_work_apps, R.string.resolver_turn_on_work_apps_explanation, (View.OnClickListener) v -> - userManager.requestQuietModeEnabled(false, mWorkProfileUserHandle)); + mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle)); return false; } if (UserHandle.myUserId() != listUserHandle.getIdentifier()) { - if (!hasCrossProfileIntents(activeListAdapter.getIntents(), + if (!mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(), UserHandle.myUserId(), listUserHandle.getIdentifier())) { if (listUserHandle == mPersonalProfileUserHandle) { showEmptyState(activeListAdapter, @@ -366,4 +393,26 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { */ void onProfileSelected(int profileIndex); } + + /** + * Describes an injector to be used for cross profile functionality. Overridable for testing. + */ + @VisibleForTesting + public interface Injector { + /** + * Returns {@code true} if at least one of the provided {@code intents} can be forwarded + * from {@code sourceUserId} to {@code targetUserId}. + */ + boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId); + + /** + * Returns whether the given profile is in quiet mode or not. + */ + boolean isQuietModeEnabled(UserHandle workProfileUserHandle); + + /** + * Enables or disables quiet mode for a managed profile. + */ + void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle); + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/inputmethod/OWNERS b/core/java/com/android/internal/inputmethod/OWNERS new file mode 100644 index 000000000000..fc0e5d4dea2b --- /dev/null +++ b/core/java/com/android/internal/inputmethod/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include ../../../../../../services/core/java/com/android/server/inputmethod/OWNERS diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 518911e652f6..0e9c2c4dd659 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -19,8 +19,6 @@ package com.android.internal.os; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ApplicationErrorReport; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; import android.compat.annotation.UnsupportedAppUsage; import android.content.type.DefaultMimeMapFactory; import android.os.Build; @@ -36,7 +34,6 @@ import android.util.Slog; import com.android.internal.logging.AndroidConfig; import com.android.server.NetworkManagementSocketTagger; -import dalvik.annotation.compat.VersionCodes; import dalvik.system.RuntimeHooks; import dalvik.system.ThreadPrioritySetter; import dalvik.system.VMRuntime; @@ -67,18 +64,8 @@ public class RuntimeInit { private static volatile boolean mCrashing = false; - /** - * Native heap allocations will now have a non-zero tag in the most significant byte. - * See - * <a href="https://source.android.com/devices/tech/debug/tagged-pointers">https://source.android.com/devices/tech/debug/tagged-pointers</a>. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = VersionCodes.Q) - private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. - private static final native void nativeFinishInit(); private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup); - private static native void nativeDisableHeapPointerTagging(); private static int Clog_e(String tag, String msg, Throwable tr) { return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr); @@ -411,20 +398,6 @@ public class RuntimeInit { if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!"); } - private static void maybeDisableHeapPointerTagging(long[] disabledCompatChanges) { - // Heap tagging needs to be disabled before any additional threads are created, but the - // AppCompat framework is not initialized enough at this point. - // Check if the change is enabled manually. - if (disabledCompatChanges != null) { - for (int i = 0; i < disabledCompatChanges.length; i++) { - if (disabledCompatChanges[i] == NATIVE_HEAP_POINTER_TAGGING) { - nativeDisableHeapPointerTagging(); - break; - } - } - } - } - protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { // If the application calls System.exit(), terminate the process @@ -437,8 +410,6 @@ public class RuntimeInit { VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges); - maybeDisableHeapPointerTagging(disabledCompatChanges); - final Arguments args = new Arguments(argv); // The end of of the RuntimeInit event (see #zygoteInit). diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index dfd700f51103..556586477218 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -122,6 +122,25 @@ public final class Zygote { */ public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18; + public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20); + /** + * Enable pointer tagging in this process. + * Tags are checked during memory deallocation, but not on access. + * TBI stands for Top-Byte-Ignore, an ARM CPU feature. + * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging} + */ + public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19; + + /** + * Enable asynchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19; + + /** + * Enable synchronous memory tag checks in this process. + */ + public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19; + /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE; /** Default external storage should be mounted. */ diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index ae54eb210de7..e34aa9722ec2 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -788,6 +788,10 @@ public class ZygoteInit { Zygote.applyDebuggerSystemProperty(parsedArgs); Zygote.applyInvokeWithSystemProperty(parsedArgs); + /* Enable pointer tagging in the system server unconditionally. Hardware support for + * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */ + parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + if (shouldProfileSystemServer()) { parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; } diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 1414cd645092..bbc23fec87ea 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -241,14 +241,6 @@ static void com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup(JNIE gCurRuntime->setExitWithoutCleanup(exitWithoutCleanup); } -static void com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging( - JNIEnv* env, jobject clazz) { - HeapTaggingLevel tag_level = M_HEAP_TAGGING_LEVEL_NONE; - if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &tag_level, sizeof(tag_level))) { - ALOGE("ERROR: could not disable heap pointer tagging\n"); - } -} - /* * JNI registration. */ @@ -260,8 +252,6 @@ int register_com_android_internal_os_RuntimeInit(JNIEnv* env) (void*)com_android_internal_os_RuntimeInit_nativeFinishInit}, {"nativeSetExitWithoutCleanup", "(Z)V", (void*)com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup}, - {"nativeDisableHeapPointerTagging", "()V", - (void*)com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging}, }; return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit", methods, NELEM(methods)); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 7a9a3f8643c0..9fbb8df0d079 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -349,6 +349,8 @@ static const std::array<const std::string, MOUNT_EXTERNAL_COUNT> ExternalStorage enum RuntimeFlags : uint32_t { DEBUG_ENABLE_JDWP = 1, PROFILE_FROM_SHELL = 1 << 15, + MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20), + MEMORY_TAG_LEVEL_TBI = 1 << 19, }; enum UnsolicitedZygoteMessageTypes : uint32_t { @@ -1627,6 +1629,16 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } + HeapTaggingLevel heap_tagging_level; + switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) { + case RuntimeFlags::MEMORY_TAG_LEVEL_TBI: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI; + break; + default: + heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; + } + android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level)); + if (NeedsNoRandomizeWorkaround()) { // Work around ARM kernel ASLR lossage (http://b/5817320). int old_personality = personality(0xffffffff); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 60b3a19c4cf3..9dc40c80f18b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5344,6 +5344,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.people.data.DataMaintenanceService" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> + <service android:name="com.android.server.autofill.AutofillCompatAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml index 6959e9c0f2bb..5ed4c5301011 100644 --- a/core/res/res/layout/resolver_empty_states.xml +++ b/core/res/res/layout/resolver_empty_states.xml @@ -34,7 +34,7 @@ android:layout_marginTop="16dp" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?attr/textAppearanceMedium" + android:fontFamily="@string/config_headlineFontFamilyMedium" android:textColor="@color/resolver_empty_state_text" android:textSize="18sp"/> <TextView @@ -52,7 +52,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" - android:textAppearance="?attr/textAppearanceMedium" + android:fontFamily="@string/config_headlineFontFamilyMedium" android:textSize="14sp" android:textColor="@color/resolver_tabs_active_color"/> </LinearLayout>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7d8b8db9d4a0..7fc041c8dbd4 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3774,7 +3774,7 @@ <attr name="animatedImageDrawable" format="reference"/> <!-- Html description of the accessibility service, to help users understand how the service can help them.--> - <attr name="htmlDescription" format="string"/> + <attr name="htmlDescription" format="reference"/> <!-- Short description of the accessibility service purpose or behavior.--> <attr name="description" /> @@ -3795,7 +3795,7 @@ <attr name="animatedImageDrawable" format="reference"/> <!-- Html description of the target of accessibility shortcut purpose or behavior, to help users understand how the target of accessibility shortcut can help them. --> - <attr name="htmlDescription" format="string"/> + <attr name="htmlDescription" format="reference"/> </declare-styleable> <!-- Use <code>print-service</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 c66261bb6630..e231c3a323fd 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1782,6 +1782,15 @@ The default value is {@code false}. --> <attr name="crossProfile" format="boolean" /> + + <!-- If {@code true} this app will receive tagged pointers to native heap allocations + from functions like malloc() on compatible devices. Note that this may not always + be respected due to policy or backwards compatibility reasons. See the + <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged Pointers</a> + document for more information on this feature. + + The default value is {@code true}. --> + <attr name="allowNativeHeapPointerTagging" format="boolean" /> </declare-styleable> <!-- The <code>feature</code> tag declares a feature. A feature is a logical part of an app. diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 4172044dfa13..0edad3b0d9cd 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3017,6 +3017,7 @@ <public name="autofillInlineSuggestionSubtitle" /> <!-- @hide @SystemApi --> <public name="isAutofillInlineSuggestionTheme" /> + <public name="allowNativeHeapPointerTagging" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> 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 ce71bebfc455..6d0e58bc89be 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -18,6 +18,7 @@ package com.android.internal.app; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; @@ -355,18 +356,14 @@ public class ChooserActivityTest { public void hasOtherProfileOneOption() throws Exception { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; - + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); markWorkProfileUserAvailable(); - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0); + Intent sendIntent = createSendTextIntent(); final ChooserWrapperActivity activity = mActivityRule .launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); @@ -382,9 +379,11 @@ public class ChooserActivityTest { // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2); + createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10); waitForIdle(); - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) + Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS); + + onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) .perform(click()); waitForIdle(); assertThat(chosen[0], is(toChoose)); @@ -1218,17 +1217,7 @@ public class ChooserActivityTest { int workProfileTargets = 4; List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( workProfileTargets); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); markWorkProfileUserAvailable(); @@ -1245,7 +1234,7 @@ public class ChooserActivityTest { } @Test - public void testWorkTab_workProfileHasExpectedNumberOfTargets() { + public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); @@ -1254,18 +1243,7 @@ public class ChooserActivityTest { createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); @@ -1284,12 +1262,12 @@ public class ChooserActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); int workProfileTargets = 4; List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); ResolveInfo[] chosen = new ResolveInfo[1]; @@ -1312,6 +1290,85 @@ public class ChooserActivityTest { assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); } + @Test + public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + sOverrides.hasCrossProfileIntents = false; + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + final ChooserWrapperActivity activity = + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + + onView(withText(R.string.resolver_cant_share_with_work_apps)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_workProfileDisabled_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + sOverrides.isQuietModeEnabled = true; + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + final ChooserWrapperActivity activity = + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_turn_on_work_apps)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_noWorkTargets_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(0); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + final ChooserWrapperActivity activity = + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_no_apps_available)) + .check(matches(isDisplayed())); + } + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); @@ -1486,4 +1543,21 @@ public class ChooserActivityTest { private void markWorkProfileUserAvailable() { sOverrides.workProfileUserHandle = UserHandle.of(10); } + + private void setupResolverControllers( + List<ResolvedComponentInfo> personalResolvedComponentInfos, + List<ResolvedComponentInfo> workResolvedComponentInfos) { + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + } } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index a68b59086d42..363551bc92fc 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -16,15 +16,10 @@ package com.android.internal.app; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.Nullable; -import android.app.prediction.AppPredictionContext; -import android.app.prediction.AppPredictionManager; -import android.app.prediction.AppPredictor; import android.app.usage.UsageStatsManager; import android.content.ContentResolver; import android.content.Context; @@ -35,7 +30,6 @@ import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; -import android.os.Bundle; import android.os.UserHandle; import android.util.Size; @@ -45,8 +39,7 @@ import com.android.internal.app.chooser.TargetInfo; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import org.mockito.Mockito; - +import java.util.List; import java.util.function.Function; public class ChooserWrapperActivity extends ChooserActivity { @@ -56,6 +49,15 @@ public class ChooserWrapperActivity extends ChooserActivity { static final OverrideData sOverrides = new OverrideData(); private UsageStatsManager mUsm; + @Override + protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( + Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) { + AbstractMultiProfilePagerAdapter multiProfilePagerAdapter = + super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed); + multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector); + return multiProfilePagerAdapter; + } + ChooserListAdapter getAdapter() { return mChooserMultiProfilePagerAdapter.getActiveListAdapter(); } @@ -206,6 +208,9 @@ public class ChooserWrapperActivity extends ChooserActivity { public int alternateProfileSetting; public Resources resources; public UserHandle workProfileUserHandle; + public boolean hasCrossProfileIntents; + public boolean isQuietModeEnabled; + public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector; public void reset() { onSafelyStartCallback = null; @@ -221,6 +226,26 @@ public class ChooserWrapperActivity extends ChooserActivity { alternateProfileSetting = 0; resources = null; workProfileUserHandle = null; + hasCrossProfileIntents = true; + isQuietModeEnabled = false; + multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() { + @Override + public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, + int targetUserId) { + return hasCrossProfileIntents; + } + + @Override + public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) { + return isQuietModeEnabled; + } + + @Override + public void requestQuietModeEnabled(boolean enabled, + UserHandle workProfileUserHandle) { + isQuietModeEnabled = enabled; + } + }; } } } diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 5f4194aa51e3..a7bf48858e42 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -18,16 +18,16 @@ package com.android.internal.app; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.android.internal.app.MatcherUtils.first; import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo; -import static com.android.internal.app.ResolverDataProvider.createResolvedComponentInfoWithOtherId; import static com.android.internal.app.ResolverWrapperActivity.sOverrides; import static org.hamcrest.CoreMatchers.allOf; @@ -56,9 +56,6 @@ import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGett import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter; import com.android.internal.widget.ResolverDrawerLayout; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -224,17 +221,14 @@ public class ResolverActivityTest { public void hasOtherProfileOneOption() throws Exception { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; - + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); markWorkProfileUserAvailable(); - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0); + Intent sendIntent = createSendImageIntent(); final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource()); waitForIdle(); @@ -249,8 +243,9 @@ public class ResolverActivityTest { }; // Make a stable copy of the components as the original list may be modified List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2); - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) + createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10); + // We pick the first one as there is another one in the work profile side + onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) .perform(click()); onView(withId(R.id.button_once)) .perform(click()); @@ -415,18 +410,14 @@ public class ResolverActivityTest { } @Test - public void testWorkTab_workTabListEmptyBeforeGoingToTab() { + public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); + createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + setupResolverControllers(personalResolvedComponentInfos, + new ArrayList<>(workResolvedComponentInfos)); Intent sendIntent = createSendImageIntent(); markWorkProfileUserAvailable(); @@ -434,8 +425,8 @@ public class ResolverActivityTest { waitForIdle(); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - // The work list adapter must only be filled when we open the work tab - assertThat(activity.getWorkListAdapter().getCount(), is(0)); + // The work list adapter must be populated in advance before tapping the other tab + assertThat(activity.getWorkListAdapter().getCount(), is(4)); } @Test @@ -445,17 +436,7 @@ public class ResolverActivityTest { List<ResolvedComponentInfo> personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); markWorkProfileUserAvailable(); @@ -474,34 +455,7 @@ public class ResolverActivityTest { List<ResolvedComponentInfo> personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); markWorkProfileUserAvailable(); @@ -521,18 +475,7 @@ public class ResolverActivityTest { List<ResolvedComponentInfo> personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); @@ -552,18 +495,7 @@ public class ResolverActivityTest { List<ResolvedComponentInfo> personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); ResolveInfo[] chosen = new ResolveInfo[1]; sOverrides.onSafelyStartCallback = targetInfo -> { @@ -597,35 +529,7 @@ public class ResolverActivityTest { List<ResolvedComponentInfo> personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(1); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(sOverrides.workProfileUserHandle))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(new ArrayList<>(workResolvedComponentInfos)); - when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))).thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); @@ -644,10 +548,10 @@ public class ResolverActivityTest { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); ResolveInfo[] chosen = new ResolveInfo[1]; sOverrides.onSafelyStartCallback = targetInfo -> { @@ -672,6 +576,82 @@ public class ResolverActivityTest { assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); } + @Test + public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + sOverrides.hasCrossProfileIntents = false; + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + + onView(withText(R.string.resolver_cant_share_with_work_apps)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_workProfileDisabled_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + int workProfileTargets = 4; + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(workProfileTargets); + sOverrides.isQuietModeEnabled = true; + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_turn_on_work_apps)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_noWorkTargets_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(0); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_no_apps_available)) + .check(matches(isDisplayed())); + } + private Intent createSendImageIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); @@ -722,4 +702,21 @@ public class ResolverActivityTest { private void markWorkProfileUserAvailable() { ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10); } + + private void setupResolverControllers( + List<ResolvedComponentInfo> personalResolvedComponentInfos, + List<ResolvedComponentInfo> workResolvedComponentInfos) { + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos); + when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class), + eq(UserHandle.SYSTEM))) + .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); + } } diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java index 36c8724e522e..208710498e1c 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java @@ -47,6 +47,15 @@ public class ResolverWrapperActivity extends ResolverActivity { filterLastUsed, createListController(userHandle), useLayoutForBrowsables, this); } + @Override + protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( + Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) { + AbstractMultiProfilePagerAdapter multiProfilePagerAdapter = + super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed); + multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector); + return multiProfilePagerAdapter; + } + ResolverWrapperAdapter getAdapter() { return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter(); } @@ -124,6 +133,9 @@ public class ResolverWrapperActivity extends ResolverActivity { public ResolverListController workResolverListController; public Boolean isVoiceInteraction; public UserHandle workProfileUserHandle; + public boolean hasCrossProfileIntents; + public boolean isQuietModeEnabled; + public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector; public void reset() { onSafelyStartCallback = null; @@ -132,6 +144,26 @@ public class ResolverWrapperActivity extends ResolverActivity { resolverListController = mock(ResolverListController.class); workResolverListController = mock(ResolverListController.class); workProfileUserHandle = null; + hasCrossProfileIntents = true; + isQuietModeEnabled = false; + multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() { + @Override + public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, + int targetUserId) { + return hasCrossProfileIntents; + } + + @Override + public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) { + return isQuietModeEnabled; + } + + @Override + public void requestQuietModeEnabled(boolean enabled, + UserHandle workProfileUserHandle) { + isQuietModeEnabled = enabled; + } + }; } } }
\ No newline at end of file diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index af115b1e80c1..4b95e4d7fa6c 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -381,6 +381,8 @@ applications that come with the platform <permission name="android.permission.REBOOT"/> <!-- Permission required for access VIBRATOR_STATE. --> <permission name="android.permission.ACCESS_VIBRATOR_STATE"/> + <!-- Permission required for UsageStatsTest CTS test. --> + <permission name="android.permission.MANAGE_NOTIFICATIONS"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 06d4fbdd85b1..ce8ff7dc38ba 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -867,7 +867,8 @@ public abstract class ColorSpace { } } - private ColorSpace( + /** @hide */ + ColorSpace( @NonNull String name, @NonNull Model model, @IntRange(from = MIN_ID, to = MAX_ID) int id) { diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java new file mode 100644 index 000000000000..f9033a53d7e6 --- /dev/null +++ b/graphics/java/android/graphics/ParcelableColorSpace.java @@ -0,0 +1,183 @@ +/* + * 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 android.graphics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A {@link Parcelable} {@link ColorSpace}. In order to enable parceling, the ColorSpace + * must be either a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} instance + * that has an ICC parametric transfer function as returned by {@link Rgb#getTransferParameters()}. + * TODO: Make public + * @hide + */ +public final class ParcelableColorSpace extends ColorSpace implements Parcelable { + private final ColorSpace mColorSpace; + + /** + * Checks if the given ColorSpace is able to be parceled. A ColorSpace can only be + * parceled if it is a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} + * instance that has an ICC parametric transfer function as returned by + * {@link Rgb#getTransferParameters()} + */ + public static boolean isParcelable(@NonNull ColorSpace colorSpace) { + if (colorSpace.getId() == ColorSpace.MIN_ID) { + if (!(colorSpace instanceof ColorSpace.Rgb)) { + return false; + } + ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace; + if (rgb.getTransferParameters() == null) { + return false; + } + } + return true; + } + + /** + * Constructs a new ParcelableColorSpace that wraps the provided ColorSpace. + * + * @param colorSpace The ColorSpace to wrap. The ColorSpace must be either named or be an + * RGB ColorSpace with an ICC parametric transfer function. + * @throws IllegalArgumentException If the provided ColorSpace does not satisfy the requirements + * to be parceled. See {@link #isParcelable(ColorSpace)}. + */ + public ParcelableColorSpace(@NonNull ColorSpace colorSpace) { + super(colorSpace.getName(), colorSpace.getModel(), colorSpace.getId()); + mColorSpace = colorSpace; + + if (mColorSpace.getId() == ColorSpace.MIN_ID) { + if (!(mColorSpace instanceof ColorSpace.Rgb)) { + throw new IllegalArgumentException( + "Unable to parcel unknown ColorSpaces that are not ColorSpace.Rgb"); + } + ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace; + if (rgb.getTransferParameters() == null) { + throw new IllegalArgumentException("ColorSpace must use an ICC " + + "parametric transfer function to be parcelable"); + } + } + } + + public @NonNull ColorSpace getColorSpace() { + return mColorSpace; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + final int id = mColorSpace.getId(); + dest.writeInt(id); + if (id == ColorSpace.MIN_ID) { + // Not a named color space. We have to actually write, like, stuff. And things. Ugh. + // Cast is safe because this was asserted in the constructor + ColorSpace.Rgb rgb = (ColorSpace.Rgb) mColorSpace; + dest.writeString(rgb.getName()); + dest.writeFloatArray(rgb.getPrimaries()); + dest.writeFloatArray(rgb.getWhitePoint()); + ColorSpace.Rgb.TransferParameters transferParameters = rgb.getTransferParameters(); + dest.writeDouble(transferParameters.a); + dest.writeDouble(transferParameters.b); + dest.writeDouble(transferParameters.c); + dest.writeDouble(transferParameters.d); + dest.writeDouble(transferParameters.e); + dest.writeDouble(transferParameters.f); + dest.writeDouble(transferParameters.g); + } + } + + @NonNull + public static final Parcelable.Creator<ParcelableColorSpace> CREATOR = + new Parcelable.Creator<ParcelableColorSpace>() { + + public @NonNull ParcelableColorSpace createFromParcel(@NonNull Parcel in) { + final int id = in.readInt(); + if (id == ColorSpace.MIN_ID) { + String name = in.readString(); + float[] primaries = in.createFloatArray(); + float[] whitePoint = in.createFloatArray(); + double a = in.readDouble(); + double b = in.readDouble(); + double c = in.readDouble(); + double d = in.readDouble(); + double e = in.readDouble(); + double f = in.readDouble(); + double g = in.readDouble(); + ColorSpace.Rgb.TransferParameters function = + new ColorSpace.Rgb.TransferParameters(a, b, c, d, e, f, g); + return new ParcelableColorSpace( + new ColorSpace.Rgb(name, primaries, whitePoint, function)); + } else { + return new ParcelableColorSpace(ColorSpace.get(id)); + } + } + + public ParcelableColorSpace[] newArray(int size) { + return new ParcelableColorSpace[size]; + } + }; + + @Override + public boolean isWideGamut() { + return mColorSpace.isWideGamut(); + } + + @Override + public float getMinValue(int component) { + return mColorSpace.getMinValue(component); + } + + @Override + public float getMaxValue(int component) { + return mColorSpace.getMaxValue(component); + } + + @Override + public @NonNull float[] toXyz(@NonNull float[] v) { + return mColorSpace.toXyz(v); + } + + @Override + public @NonNull float[] fromXyz(@NonNull float[] v) { + return mColorSpace.fromXyz(v); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ParcelableColorSpace other = (ParcelableColorSpace) o; + return mColorSpace.equals(other.mColorSpace); + } + + @Override + public int hashCode() { + return mColorSpace.hashCode(); + } + + /** @hide */ + @Override + long getNativeInstance() { + return mColorSpace.getNativeInstance(); + } +} diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index 89a9b997af97..84c07d7d9dff 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -16,16 +16,16 @@ #include "Readback.h" -#include "pipeline/skia/LayerDrawable.h" -#include "renderthread/EglManager.h" -#include "renderthread/VulkanManager.h" - -#include <gui/Surface.h> -#include <ui/Fence.h> +#include <sync/sync.h> +#include <system/window.h> #include <ui/GraphicBuffer.h> + #include "DeferredLayerUpdater.h" #include "Properties.h" #include "hwui/Bitmap.h" +#include "pipeline/skia/LayerDrawable.h" +#include "renderthread/EglManager.h" +#include "renderthread/VulkanManager.h" #include "utils/Color.h" #include "utils/MathUtils.h" #include "utils/TraceUtils.h" @@ -35,40 +35,43 @@ using namespace android::uirenderer::renderthread; namespace android { namespace uirenderer { -CopyResult Readback::copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap) { +CopyResult Readback::copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap) { ATRACE_CALL(); // Setup the source - sp<GraphicBuffer> sourceBuffer; - sp<Fence> sourceFence; + AHardwareBuffer* rawSourceBuffer; + int rawSourceFence; Matrix4 texTransform; - status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, texTransform.data); + status_t err = ANativeWindow_getLastQueuedBuffer(window, &rawSourceBuffer, &rawSourceFence, + texTransform.data); + base::unique_fd sourceFence(rawSourceFence); texTransform.invalidateType(); if (err != NO_ERROR) { ALOGW("Failed to get last queued buffer, error = %d", err); return CopyResult::UnknownError; } - if (!sourceBuffer.get()) { + if (rawSourceBuffer == nullptr) { ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); return CopyResult::SourceEmpty; } - if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) { + + std::unique_ptr<AHardwareBuffer, decltype(&AHardwareBuffer_release)> sourceBuffer( + rawSourceBuffer, AHardwareBuffer_release); + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(sourceBuffer.get(), &description); + if (description.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) { ALOGW("Surface is protected, unable to copy from it"); return CopyResult::SourceInvalid; } - err = sourceFence->wait(500 /* ms */); - if (err != NO_ERROR) { + + if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) { ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); return CopyResult::Timeout; } - if (!sourceBuffer.get()) { - return CopyResult::UnknownError; - } - sk_sp<SkColorSpace> colorSpace = - DataSpaceToColorSpace(static_cast<android_dataspace>(surface.getBuffersDataSpace())); - sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer( - reinterpret_cast<AHardwareBuffer*>(sourceBuffer.get()), - kPremul_SkAlphaType, colorSpace); + sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace( + static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window))); + sk_sp<SkImage> image = + SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace); return copyImageInto(image, texTransform, srcRect, bitmap); } diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h index e86a8136cfa3..e36f1ff6a072 100644 --- a/libs/hwui/Readback.h +++ b/libs/hwui/Readback.h @@ -47,7 +47,7 @@ public: /** * Copies the surface's most recently queued buffer into the provided bitmap. */ - CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap); + CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap); CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 91f9447a3d59..1df3336bb5e5 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -204,8 +204,7 @@ void CanvasContext::setStopped(bool stopped) { void CanvasContext::allocateBuffers() { if (mNativeSurface) { - ANativeWindow* anw = mNativeSurface->getNativeWindow(); - ANativeWindow_allocateBuffers(anw); + ANativeWindow_tryAllocateBuffers(mNativeSurface->getNativeWindow()); } } diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index f9e401a2e93b..1e7fc71a7f04 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -317,8 +317,9 @@ void RenderProxy::setRenderAheadDepth(int renderAhead) { int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap) { auto& thread = RenderThread::getInstance(); + ANativeWindow* window = surface.get(); return static_cast<int>(thread.queue().runSync([&]() -> auto { - return thread.readback().copySurfaceInto(*surface, Rect(left, top, right, bottom), bitmap); + return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap); })); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 4683e1d69019..ab0dd2bcc8f5 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -140,6 +140,10 @@ public: */ ANDROID_API void setRenderAheadDepth(int renderAhead); + // TODO: This api will need to take in an ANativeWindow instead, but the + // caller, ThreadedRenderer, doesn't have access to libandroid due to a + // circular dependency, so it can't use the JNI ANativeWindow methods. Once + // that is resolved then replace the surface type here. ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap); ANDROID_API static void prepareToDraw(Bitmap& bitmap); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 0e6ade539e35..2178393cdd5a 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -875,12 +875,12 @@ public class MediaRouter2 { } /** - * @return the unmodifiable list of transferrable routes for the session. + * @return the unmodifiable list of transferable routes for the session. */ @NonNull - public List<MediaRoute2Info> getTransferrableRoutes() { + public List<MediaRoute2Info> getTransferableRoutes() { synchronized (mControllerLock) { - return getRoutesWithIdsLocked(mSessionInfo.getTransferrableRoutes()); + return getRoutesWithIdsLocked(mSessionInfo.getTransferableRoutes()); } } @@ -1033,12 +1033,12 @@ public class MediaRouter2 { * all of the following conditions: * <ul> * <li>ID should not be included in {@link #getSelectedRoutes()}</li> - * <li>ID should be included in {@link #getTransferrableRoutes()}</li> + * <li>ID should be included in {@link #getTransferableRoutes()}</li> * </ul> * If the route doesn't meet any of above conditions, it will be ignored. * * @see #getSelectedRoutes() - * @see #getTransferrableRoutes() + * @see #getTransferableRoutes() * @see RoutingControllerCallback#onControllerUpdated */ public void transferToRoute(@NonNull MediaRoute2Info route) { @@ -1057,9 +1057,9 @@ public class MediaRouter2 { return; } - List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes(); - if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) { - Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route); + List<MediaRoute2Info> transferableRoutes = getTransferableRoutes(); + if (!checkRouteListContainsRouteId(transferableRoutes, route.getId())) { + Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route); return; } @@ -1156,7 +1156,7 @@ public class MediaRouter2 { .map(MediaRoute2Info::getId).collect(Collectors.toList()); List<String> deselectableRoutes = getDeselectableRoutes().stream() .map(MediaRoute2Info::getId).collect(Collectors.toList()); - List<String> transferrableRoutes = getTransferrableRoutes().stream() + List<String> transferableRoutes = getTransferableRoutes().stream() .map(MediaRoute2Info::getId).collect(Collectors.toList()); StringBuilder result = new StringBuilder() @@ -1171,8 +1171,8 @@ public class MediaRouter2 { .append(", deselectableRoutes={") .append(deselectableRoutes) .append("}") - .append(", transferrableRoutes={") - .append(transferrableRoutes) + .append(", transferableRoutes={") + .append(transferableRoutes) .append("}") .append(" }"); return result.toString(); diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 5ce291c06ade..4801d44bdb10 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -259,7 +259,7 @@ public class MediaRouter2Manager { /** * Selects media route for the specified package name. * - * If the given route is {@link RoutingController#getTransferrableRoutes() a transferrable + * If the given route is {@link RoutingController#getTransferableRoutes() a transferable * route} of a routing session of the application, the session will be transferred to * the route. If not, a new routing session will be created. * @@ -274,7 +274,7 @@ public class MediaRouter2Manager { //TODO: instead of release all controllers, add an API to specify controllers that // should be released (or is the system controller). for (RoutingSessionInfo sessionInfo : getRoutingSessions(packageName)) { - if (!transferred && sessionInfo.getTransferrableRoutes().contains(route.getId())) { + if (!transferred && sessionInfo.getTransferableRoutes().contains(route.getId())) { new RoutingController(sessionInfo).transferToRoute(route); transferred = true; } else if (!sessionInfo.isSystemSession()) { @@ -543,13 +543,13 @@ public class MediaRouter2Manager { } /** - * @return the unmodifiable list of transferrable routes for the session. + * @return the unmodifiable list of transferable routes for the session. */ @NonNull - public List<MediaRoute2Info> getTransferrableRoutes() { + public List<MediaRoute2Info> getTransferableRoutes() { List<String> routeIds; synchronized (mControllerLock) { - routeIds = mSessionInfo.getTransferrableRoutes(); + routeIds = mSessionInfo.getTransferableRoutes(); } return getRoutesWithIds(routeIds); } @@ -643,12 +643,12 @@ public class MediaRouter2Manager { * all of the following conditions: * <ul> * <li>ID should not be included in {@link #getSelectedRoutes()}</li> - * <li>ID should be included in {@link #getTransferrableRoutes()}</li> + * <li>ID should be included in {@link #getTransferableRoutes()}</li> * </ul> * If the route doesn't meet any of above conditions, it will be ignored. * * @see #getSelectedRoutes() - * @see #getTransferrableRoutes() + * @see #getTransferableRoutes() */ public void transferToRoute(@NonNull MediaRoute2Info route) { Objects.requireNonNull(route, "route must not be null"); @@ -663,8 +663,8 @@ public class MediaRouter2Manager { return; } - if (!sessionInfo.getTransferrableRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route); + if (!sessionInfo.getTransferableRoutes().contains(route.getId())) { + Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route); return; } diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 05fa511fc81a..2bffe8ac0649 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -156,9 +156,7 @@ public class MediaScannerConnection implements ServiceConnection { } BackgroundThread.getExecutor().execute(() -> { final Uri uri = scanFileQuietly(mProvider, new File(path)); - if (mClient != null) { - mClient.onScanCompleted(path, uri); - } + runCallBack(mContext, mClient, path, uri); }); } } @@ -187,9 +185,7 @@ public class MediaScannerConnection implements ServiceConnection { .acquireContentProviderClient(MediaStore.AUTHORITY)) { for (String path : paths) { final Uri uri = scanFileQuietly(client, new File(path)); - if (callback != null) { - callback.onScanCompleted(path, uri); - } + runCallBack(context, callback, path, uri); } } }); @@ -206,6 +202,23 @@ public class MediaScannerConnection implements ServiceConnection { return uri; } + private static void runCallBack(Context context, OnScanCompletedListener callback, + String path, Uri uri) { + if (callback != null) { + // Ignore exceptions from callback to avoid calling app from crashing. + // Don't ignore exceptions for apps targeting 'R' or higher. + try { + callback.onScanCompleted(path, uri); + } catch (Throwable e) { + if (context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R) { + throw e; + } else { + Log.w(TAG, "Ignoring exception from callback for backward compatibility", e); + } + } + } + } + @Deprecated static class ClientProxy implements MediaScannerConnectionClient { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java index ebcb9ed342e5..2e038e665520 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -31,8 +31,16 @@ import java.util.Objects; import java.util.Set; /** - * A media route discovery preference describing the kinds of routes that media router + * A media route discovery preference describing the features of routes that media router * would like to discover and whether to perform active scanning. + * <p> + * When {@link MediaRouter2} instances set discovery preferences by calling + * {@link MediaRouter2#registerRouteCallback}, they are merged into a single discovery preference + * and it is delivered to call {@link MediaRoute2ProviderService#onDiscoveryPreferenceChanged}. + * </p><p> + * According to the given discovery preference, {@link MediaRoute2ProviderService} discovers + * routes and publishes them. + * </p> * * @see MediaRouter2#registerRouteCallback */ @@ -53,12 +61,12 @@ public final class RouteDiscoveryPreference implements Parcelable { @NonNull private final List<String> mPreferredFeatures; - private final boolean mActiveScan; + private final boolean mShouldPerformActiveScan; @Nullable private final Bundle mExtras; /** - * An empty discovery preference. + * An empty discovery preference * @hide */ public static final RouteDiscoveryPreference EMPTY = @@ -66,23 +74,39 @@ public final class RouteDiscoveryPreference implements Parcelable { RouteDiscoveryPreference(@NonNull Builder builder) { mPreferredFeatures = builder.mPreferredFeatures; - mActiveScan = builder.mActiveScan; + mShouldPerformActiveScan = builder.mActiveScan; mExtras = builder.mExtras; } RouteDiscoveryPreference(@NonNull Parcel in) { mPreferredFeatures = in.createStringArrayList(); - mActiveScan = in.readBoolean(); + mShouldPerformActiveScan = in.readBoolean(); mExtras = in.readBundle(); } + /** + * Gets the features of routes that media router would like to discover. + * <p> + * Routes that have at least one of the features will be discovered. + * They may include predefined features such as + * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO}, + * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider. + * </p> + */ @NonNull public List<String> getPreferredFeatures() { return mPreferredFeatures; } - public boolean isActiveScan() { - return mActiveScan; + /** + * Gets whether active scanning should be performed. + * <p> + * If any of discovery preferences sets this as {@code true}, active scanning will + * be performed regardless of other discovery preferences. + * </p> + */ + public boolean shouldPerformActiveScan() { + return mShouldPerformActiveScan; } /** @@ -100,7 +124,7 @@ public final class RouteDiscoveryPreference implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeStringList(mPreferredFeatures); - dest.writeBoolean(mActiveScan); + dest.writeBoolean(mShouldPerformActiveScan); dest.writeBundle(mExtras); } @@ -112,7 +136,7 @@ public final class RouteDiscoveryPreference implements Parcelable { .append(String.join(", ", mPreferredFeatures)) .append("}") .append(", activeScan=") - .append(mActiveScan) + .append(mShouldPerformActiveScan) .append(" }"); return result.toString(); @@ -128,12 +152,12 @@ public final class RouteDiscoveryPreference implements Parcelable { } RouteDiscoveryPreference other = (RouteDiscoveryPreference) o; return Objects.equals(mPreferredFeatures, other.mPreferredFeatures) - && mActiveScan == other.mActiveScan; + && mShouldPerformActiveScan == other.mShouldPerformActiveScan; } @Override public int hashCode() { - return Objects.hash(mPreferredFeatures, mActiveScan); + return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan); } /** @@ -154,12 +178,12 @@ public final class RouteDiscoveryPreference implements Parcelable { Objects.requireNonNull(preference, "preference must not be null"); mPreferredFeatures = preference.getPreferredFeatures(); - mActiveScan = preference.isActiveScan(); + mActiveScan = preference.shouldPerformActiveScan(); mExtras = preference.getExtras(); } /** - * A constructor to combine all of the preferences into a single preference . + * A constructor to combine all of the preferences into a single preference. * It ignores extras of preferences. * * @hide @@ -171,13 +195,19 @@ public final class RouteDiscoveryPreference implements Parcelable { mActiveScan = false; for (RouteDiscoveryPreference preference : preferences) { routeFeatureSet.addAll(preference.mPreferredFeatures); - mActiveScan |= preference.mActiveScan; + mActiveScan |= preference.mShouldPerformActiveScan; } mPreferredFeatures = new ArrayList<>(routeFeatureSet); } /** * Sets preferred route features to discover. + * @param preferredFeatures features of routes that media router would like to discover. + * May include predefined features + * such as {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, + * {@link MediaRoute2Info#FEATURE_LIVE_VIDEO}, + * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} + * or custom features defined by a provider. */ @NonNull public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) { @@ -188,9 +218,13 @@ public final class RouteDiscoveryPreference implements Parcelable { /** * Sets if active scanning should be performed. + * <p> + * Since active scanning uses more system resources, set this as {@code true} only + * when it's necessary. + * </p> */ @NonNull - public Builder setActiveScan(boolean activeScan) { + public Builder setShouldPerformActiveScan(boolean activeScan) { mActiveScan = activeScan; return this; } diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java index 0d4e666de51b..19a46ce6570d 100644 --- a/media/java/android/media/RoutingSessionInfo.java +++ b/media/java/android/media/RoutingSessionInfo.java @@ -55,7 +55,7 @@ public final class RoutingSessionInfo implements Parcelable { final List<String> mSelectedRoutes; final List<String> mSelectableRoutes; final List<String> mDeselectableRoutes; - final List<String> mTransferrableRoutes; + final List<String> mTransferableRoutes; final int mVolumeHandling; final int mVolumeMax; @@ -79,8 +79,8 @@ public final class RoutingSessionInfo implements Parcelable { convertToUniqueRouteIds(builder.mSelectableRoutes)); mDeselectableRoutes = Collections.unmodifiableList( convertToUniqueRouteIds(builder.mDeselectableRoutes)); - mTransferrableRoutes = Collections.unmodifiableList( - convertToUniqueRouteIds(builder.mTransferrableRoutes)); + mTransferableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mTransferableRoutes)); mVolumeHandling = builder.mVolumeHandling; mVolumeMax = builder.mVolumeMax; @@ -100,7 +100,7 @@ public final class RoutingSessionInfo implements Parcelable { mSelectedRoutes = ensureList(src.createStringArrayList()); mSelectableRoutes = ensureList(src.createStringArrayList()); mDeselectableRoutes = ensureList(src.createStringArrayList()); - mTransferrableRoutes = ensureList(src.createStringArrayList()); + mTransferableRoutes = ensureList(src.createStringArrayList()); mVolumeHandling = src.readInt(); mVolumeMax = src.readInt(); @@ -193,11 +193,11 @@ public final class RoutingSessionInfo implements Parcelable { } /** - * Gets the list of ids of transferrable routes for the session. + * Gets the list of ids of transferable routes for the session. */ @NonNull - public List<String> getTransferrableRoutes() { - return mTransferrableRoutes; + public List<String> getTransferableRoutes() { + return mTransferableRoutes; } /** @@ -260,7 +260,7 @@ public final class RoutingSessionInfo implements Parcelable { dest.writeStringList(mSelectedRoutes); dest.writeStringList(mSelectableRoutes); dest.writeStringList(mDeselectableRoutes); - dest.writeStringList(mTransferrableRoutes); + dest.writeStringList(mTransferableRoutes); dest.writeInt(mVolumeHandling); dest.writeInt(mVolumeMax); dest.writeInt(mVolume); @@ -284,7 +284,7 @@ public final class RoutingSessionInfo implements Parcelable { && Objects.equals(mSelectedRoutes, other.mSelectedRoutes) && Objects.equals(mSelectableRoutes, other.mSelectableRoutes) && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes) - && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes) + && Objects.equals(mTransferableRoutes, other.mTransferableRoutes) && (mVolumeHandling == other.mVolumeHandling) && (mVolumeMax == other.mVolumeMax) && (mVolume == other.mVolume); @@ -293,7 +293,7 @@ public final class RoutingSessionInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mClientPackageName, mProviderId, - mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes, + mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes, mVolumeMax, mVolumeHandling, mVolume); } @@ -311,8 +311,8 @@ public final class RoutingSessionInfo implements Parcelable { .append(", deselectableRoutes={") .append(String.join(",", mDeselectableRoutes)) .append("}") - .append(", transferrableRoutes={") - .append(String.join(",", mTransferrableRoutes)) + .append(", transferableRoutes={") + .append(String.join(",", mTransferableRoutes)) .append("}") .append(", volumeHandling=").append(getVolumeHandling()) .append(", volumeMax=").append(getVolumeMax()) @@ -350,7 +350,7 @@ public final class RoutingSessionInfo implements Parcelable { final List<String> mSelectedRoutes; final List<String> mSelectableRoutes; final List<String> mDeselectableRoutes; - final List<String> mTransferrableRoutes; + final List<String> mTransferableRoutes; int mVolumeHandling = MediaRoute2Info.PLAYBACK_VOLUME_FIXED; int mVolumeMax; int mVolume; @@ -381,7 +381,7 @@ public final class RoutingSessionInfo implements Parcelable { mSelectedRoutes = new ArrayList<>(); mSelectableRoutes = new ArrayList<>(); mDeselectableRoutes = new ArrayList<>(); - mTransferrableRoutes = new ArrayList<>(); + mTransferableRoutes = new ArrayList<>(); } /** @@ -400,7 +400,7 @@ public final class RoutingSessionInfo implements Parcelable { mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes); mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes); mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes); - mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes); + mTransferableRoutes = new ArrayList<>(sessionInfo.mTransferableRoutes); mVolumeHandling = sessionInfo.mVolumeHandling; mVolumeMax = sessionInfo.mVolumeMax; @@ -524,35 +524,35 @@ public final class RoutingSessionInfo implements Parcelable { } /** - * Clears the transferrable routes. + * Clears the transferable routes. */ @NonNull - public Builder clearTransferrableRoutes() { - mTransferrableRoutes.clear(); + public Builder clearTransferableRoutes() { + mTransferableRoutes.clear(); return this; } /** - * Adds a route to the transferrable routes. The {@code routeId} must not be empty. + * Adds a route to the transferable routes. The {@code routeId} must not be empty. */ @NonNull - public Builder addTransferrableRoute(@NonNull String routeId) { + public Builder addTransferableRoute(@NonNull String routeId) { if (TextUtils.isEmpty(routeId)) { throw new IllegalArgumentException("routeId must not be empty"); } - mTransferrableRoutes.add(routeId); + mTransferableRoutes.add(routeId); return this; } /** - * Removes a route from the transferrable routes. The {@code routeId} must not be empty. + * Removes a route from the transferable routes. The {@code routeId} must not be empty. */ @NonNull - public Builder removeTransferrableRoute(@NonNull String routeId) { + public Builder removeTransferableRoute(@NonNull String routeId) { if (TextUtils.isEmpty(routeId)) { throw new IllegalArgumentException("routeId must not be empty"); } - mTransferrableRoutes.remove(routeId); + mTransferableRoutes.remove(routeId); return this; } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 9b183a3e0e92..44142e324962 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -532,6 +532,8 @@ public class Tuner implements AutoCloseable { Filter filter = nativeOpenFilter( mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize); if (filter != null) { + filter.setMainType(mainType); + filter.setSubtype(subType); filter.setCallback(cb); if (mHandler == null) { mHandler = createEventHandler(); diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index 06de6e8a83c7..a98183bbf1a3 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.TunerConstants.Result; +import android.media.tv.tuner.TunerUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -177,6 +178,8 @@ public class Filter implements AutoCloseable { private long mNativeContext; private FilterCallback mCallback; private final int mId; + private int mMainType; + private int mSubtype; private native int nativeConfigureFilter( int type, int subType, FilterConfiguration settings); @@ -197,6 +200,15 @@ public class Filter implements AutoCloseable { } /** @hide */ + public void setMainType(@Type int mainType) { + mMainType = mainType; + } + /** @hide */ + public void setSubtype(@Subtype int subtype) { + mSubtype = subtype; + } + + /** @hide */ public void setCallback(FilterCallback cb) { mCallback = cb; } @@ -213,10 +225,13 @@ public class Filter implements AutoCloseable { */ @Result public int configure(@NonNull FilterConfiguration config) { - int subType = -1; + // TODO: validate main type, subtype, config, settings + int subType; Settings s = config.getSettings(); if (s != null) { subType = s.getType(); + } else { + subType = TunerUtils.getFilterSubtype(mMainType, mSubtype); } return nativeConfigureFilter(config.getType(), subType, config); } diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java index bf5aaeda4742..a8dbfa5b11ec 100644 --- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java @@ -160,6 +160,12 @@ public class IpFilterConfiguration extends FilterConfiguration { */ @NonNull public IpFilterConfiguration build() { + int ipAddrLength = mSrcIpAddress.length; + if (ipAddrLength != mDstIpAddress.length || (ipAddrLength != 4 && ipAddrLength != 16)) { + throw new IllegalArgumentException( + "The lengths of src and dst IP address must be 4 or 16 and must be the same." + + "srcLength=" + ipAddrLength + ", dstLength=" + mDstIpAddress.length); + } return new IpFilterConfiguration( mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough); } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 44f9b7c6eb39..ac59003de5e6 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -32,6 +32,7 @@ using ::android::hardware::hidl_vec; using ::android::hardware::tv::tuner::V1_0::DataFormat; using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings; using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterType; +using ::android::hardware::tv::tuner::V1_0::DemuxAlpLengthType; using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings; using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings; using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType; @@ -40,6 +41,7 @@ using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings; using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionBits; using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings; using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings; +using ::android::hardware::tv::tuner::V1_0::DemuxIpAddress; using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings; using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterType; using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings; @@ -129,6 +131,9 @@ struct fields_t { static fields_t gFields; +static int IP_V4_LENGTH = 4; +static int IP_V6_LENGTH = 16; + namespace android { /////////////// LnbCallback /////////////////////// LnbCallback::LnbCallback(jweak tunerObj, LnbId id) : mObject(tunerObj), mId(id) {} @@ -1509,12 +1514,67 @@ static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const return filterDownloadSettings; } +static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config) { + jclass clazz = env->FindClass("android/media/tv/tuner/filter/IpFilterConfiguration"); + + jbyteArray jsrcIpAddress = static_cast<jbyteArray>( + env->GetObjectField(config, env->GetFieldID(clazz, "mSrcIpAddress", "[B"))); + jsize srcSize = env->GetArrayLength(jsrcIpAddress); + jbyteArray jdstIpAddress = static_cast<jbyteArray>( + env->GetObjectField(config, env->GetFieldID(clazz, "mDstIpAddress", "[B"))); + jsize dstSize = env->GetArrayLength(jdstIpAddress); + + DemuxIpAddress res; + + if (srcSize != dstSize) { + // should never happen. Validated on Java size. + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "IP address lengths don't match. srcLength=%d, dstLength=%d", srcSize, dstSize); + return res; + } + + if (srcSize == IP_V4_LENGTH) { + uint8_t srcAddr[IP_V4_LENGTH]; + uint8_t dstAddr[IP_V4_LENGTH]; + env->GetByteArrayRegion( + jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr)); + env->GetByteArrayRegion( + jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr)); + res.srcIpAddress.v4(srcAddr); + res.dstIpAddress.v4(dstAddr); + } else if (srcSize == IP_V6_LENGTH) { + uint8_t srcAddr[IP_V6_LENGTH]; + uint8_t dstAddr[IP_V6_LENGTH]; + env->GetByteArrayRegion( + jsrcIpAddress, 0, srcSize, reinterpret_cast<jbyte*>(srcAddr)); + env->GetByteArrayRegion( + jdstIpAddress, 0, dstSize, reinterpret_cast<jbyte*>(dstAddr)); + res.srcIpAddress.v6(srcAddr); + res.dstIpAddress.v6(dstAddr); + } else { + // should never happen. Validated on Java size. + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid IP address length %d", srcSize); + return res; + } + + uint16_t srcPort = static_cast<uint16_t>( + env->GetIntField(config, env->GetFieldID(clazz, "mSrcPort", "I"))); + uint16_t dstPort = static_cast<uint16_t>( + env->GetIntField(config, env->GetFieldID(clazz, "mDstPort", "I"))); + + res.srcPort = srcPort; + res.dstPort = dstPort; + + return res; +} + static DemuxFilterSettings getFilterConfiguration( - JNIEnv *env, int type, int subtype, jobject filterSettingsObj) { + JNIEnv *env, int type, int subtype, jobject filterConfigObj) { DemuxFilterSettings filterSettings; jobject settingsObj = env->GetObjectField( - filterSettingsObj, + filterConfigObj, env->GetFieldID( env->FindClass("android/media/tv/tuner/filter/FilterConfiguration"), "mSettings", @@ -1523,9 +1583,10 @@ static DemuxFilterSettings getFilterConfiguration( switch (mainType) { case DemuxFilterMainType::TS: { jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration"); - int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I")); + uint16_t tpid = static_cast<uint16_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mTpid", "I"))); DemuxTsFilterSettings tsFilterSettings { - .tpid = static_cast<uint16_t>(tpid), + .tpid = tpid, }; DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); @@ -1553,7 +1614,12 @@ static DemuxFilterSettings getFilterConfiguration( break; } case DemuxFilterMainType::MMTP: { - DemuxMmtpFilterSettings mmtpFilterSettings; + jclass clazz = env->FindClass("android/media/tv/tuner/filter/MmtpFilterConfiguration"); + uint16_t mmtpPid = static_cast<uint16_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mMmtpPid", "I"))); + DemuxMmtpFilterSettings mmtpFilterSettings { + .mmtpPid = mmtpPid, + }; DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); switch (mmtpType) { case DemuxMmtpFilterType::SECTION: @@ -1583,43 +1649,79 @@ static DemuxFilterSettings getFilterConfiguration( break; } case DemuxFilterMainType::IP: { - DemuxIpFilterSettings ipFilterSettings; + DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj); + + DemuxIpFilterSettings ipFilterSettings { + .ipAddr = ipAddr, + }; DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype); switch (ipType) { - case DemuxIpFilterType::SECTION: + case DemuxIpFilterType::SECTION: { ipFilterSettings.filterSettings.section( getFilterSectionSettings(env, settingsObj)); break; - case DemuxIpFilterType::IP: - // TODO: handle passthrough - ipFilterSettings.filterSettings.bPassthrough(false); + } + case DemuxIpFilterType::IP: { + jclass clazz = env->FindClass( + "android/media/tv/tuner/filter/IpFilterConfiguration"); + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + ipFilterSettings.filterSettings.bPassthrough(bPassthrough); break; - default: + } + default: { break; + } } filterSettings.ip(ipFilterSettings); break; } case DemuxFilterMainType::TLV: { - DemuxTlvFilterSettings tlvFilterSettings; + jclass clazz = env->FindClass("android/media/tv/tuner/filter/TlvFilterConfiguration"); + uint8_t packetType = static_cast<uint8_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I"))); + bool isCompressedIpPacket = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID(clazz, "mIsCompressedIpPacket", "Z"))); + + DemuxTlvFilterSettings tlvFilterSettings { + .packetType = packetType, + .isCompressedIpPacket = isCompressedIpPacket, + }; DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype); switch (tlvType) { - case DemuxTlvFilterType::SECTION: + case DemuxTlvFilterType::SECTION: { tlvFilterSettings.filterSettings.section( getFilterSectionSettings(env, settingsObj)); break; - case DemuxTlvFilterType::TLV: - // TODO: handle passthrough - tlvFilterSettings.filterSettings.bPassthrough(false); + } + case DemuxTlvFilterType::TLV: { + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); break; - default: + } + default: { break; + } } filterSettings.tlv(tlvFilterSettings); break; } case DemuxFilterMainType::ALP: { - DemuxAlpFilterSettings alpFilterSettings; + jclass clazz = env->FindClass("android/media/tv/tuner/filter/AlpFilterConfiguration"); + uint8_t packetType = static_cast<uint8_t>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mPacketType", "I"))); + DemuxAlpLengthType lengthType = static_cast<DemuxAlpLengthType>( + env->GetIntField(filterConfigObj, env->GetFieldID(clazz, "mLengthType", "I"))); + DemuxAlpFilterSettings alpFilterSettings { + .packetType = packetType, + .lengthType = lengthType, + }; DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); switch (alpType) { case DemuxAlpFilterType::SECTION: diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 3ffb9514a98b..615dc48b969c 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -70,8 +70,8 @@ import java.util.function.Predicate; @RunWith(AndroidJUnit4.class) @SmallTest -public class MediaRouterManagerTest { - private static final String TAG = "MediaRouterManagerTest"; +public class MediaRouter2ManagerTest { + private static final String TAG = "MediaRouter2ManagerTest"; private static final int TIMEOUT_MS = 5000; private Context mContext; diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java index 267927ff4a6e..f1dcf3dfd307 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java @@ -191,7 +191,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(sessionId, packageName) .addSelectedRoute(routeId) .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT) - .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO) + .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO) .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(SESSION_VOLUME_MAX) .setVolume(SESSION_VOLUME_INITIAL) @@ -300,7 +300,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .clearSelectedRoutes() .addSelectedRoute(routeId) .removeDeselectableRoute(routeId) - .removeTransferrableRoute(routeId) + .removeTransferableRoute(routeId) .build(); notifySessionUpdated(newSessionInfo); publishRoutes(); diff --git a/media/tests/TunerTest/Android.bp b/media/tests/TunerTest/Android.bp new file mode 100644 index 000000000000..cef879112225 --- /dev/null +++ b/media/tests/TunerTest/Android.bp @@ -0,0 +1,18 @@ +android_test { + name: "mediatunertest", + + srcs: ["**/*.java"], + + libs: [ + "android.test.runner", + "android.test.base", + ], + + static_libs: [ + "android-support-test", + "testng" + ], + + platform_apis: true, + certificate: "platform", +} diff --git a/media/tests/TunerTest/AndroidManifest.xml b/media/tests/TunerTest/AndroidManifest.xml new file mode 100644 index 000000000000..17e9f197b68c --- /dev/null +++ b/media/tests/TunerTest/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.mediatunertest"> + + <uses-permission android:name="android.permission.ACCESS_TV_TUNER" /> + + <application android:label="@string/app_name"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.mediatunertest" + android:label="Media Tuner Tests"/> +</manifest> diff --git a/media/tests/TunerTest/AndroidTest.xml b/media/tests/TunerTest/AndroidTest.xml new file mode 100644 index 000000000000..d9c31f45532f --- /dev/null +++ b/media/tests/TunerTest/AndroidTest.xml @@ -0,0 +1,17 @@ +<configuration description="Runs Media Tuner tests."> + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="MediaTunerTest"/> + + <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="mediatunertest.apk"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.mediatunertest"/> + <option name="hidden-api-checks" value="false"/> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/media/tests/TunerTest/res/values/strings.xml b/media/tests/TunerTest/res/values/strings.xml new file mode 100644 index 000000000000..b313944ed256 --- /dev/null +++ b/media/tests/TunerTest/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- name of the app [CHAR LIMIT=25]--> + <string name="app_name">MediaTunerTest</string> +</resources>
\ No newline at end of file diff --git a/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java new file mode 100644 index 000000000000..cbfbf77d11f5 --- /dev/null +++ b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 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.mediatunertest; + +import static org.junit.Assert.assertNotNull; + +import android.content.Context; +import android.media.tv.tuner.Descrambler; +import android.media.tv.tuner.Tuner; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TunerTest { + private static final String TAG = "MediaTunerTest"; + + private Context mContext; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + } + + @After + public void tearDown() { + } + + @Test + public void testTunerConstructor() throws Exception { + Tuner tuner = new Tuner(mContext, "123", 1, null); + assertNotNull(tuner); + } + + @Test + public void testOpenDescrambler() throws Exception { + Tuner tuner = new Tuner(mContext, "123", 1, null); + Descrambler descrambler = tuner.openDescrambler(); + assertNotNull(descrambler); + } +} diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 62124934f416..3f42ad40eb8e 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -27,6 +27,7 @@ android_library { "SettingsLibRadioButtonPreference", "WifiTrackerLib", "SettingsLibDisplayDensityUtils", + "SettingsLibSchedulesProvider", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml index e6f8c01e22ac..dc6dddb431b4 100644 --- a/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml +++ b/packages/SettingsLib/LayoutPreference/res/layout/cross_profiles_settings_entity_header.xml @@ -18,75 +18,39 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/entity_header" - style="@style/EntityHeader" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:orientation="horizontal"> + style="@style/EntityHeader"> <LinearLayout android:id="@+id/entity_header_content" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingTop="32dp" + android:paddingBottom="32dp" android:layout_centerHorizontal="true" - android:gravity="center_horizontal" - android:orientation="horizontal"> + android:orientation="vertical"> <LinearLayout android:id="@+id/entity_header_content" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:gravity="center_horizontal" - android:orientation="vertical"> + android:layout_gravity="center" + android:orientation="horizontal"> <ImageView android:id="@+id/entity_header_icon_personal" - android:layout_width="48dp" - android:layout_height="48dp" - android:scaleType="fitCenter" - android:antialias="true"/> + style="@style/CrossProfileEntityHeaderIcon" /> - <TextView - android:id="@+id/install_type" - style="@style/TextAppearance.EntityHeaderSummary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="2dp" - android:text="Personal"/> - </LinearLayout> - - <ImageView - android:id="@+id/entity_header_swap_horiz" - android:layout_width="24dp" - android:layout_height="24dp" - android:scaleType="fitCenter" - android:antialias="true" - android:src="@drawable/ic_swap_horiz_grey"/> - - <LinearLayout - android:id="@+id/entity_header_content" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:gravity="center_horizontal" - android:orientation="vertical"> + <ImageView + android:id="@+id/entity_header_swap_horiz" + style="@style/CrossProfileSwapHorizIcon "/> <ImageView android:id="@+id/entity_header_icon_work" - android:layout_width="48dp" - android:layout_height="48dp" - android:scaleType="fitCenter" - android:antialias="true"/> - <TextView - android:id="@+id/install_type" - style="@style/TextAppearance.EntityHeaderSummary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="2dp" - android:text="Work"/> + style="@style/CrossProfileEntityHeaderIcon" /> </LinearLayout> - </LinearLayout> + <TextView + android:id="@+id/entity_header_title" + style="@style/CrossProfileEntityHeaderTitle" /> + </LinearLayout> </RelativeLayout> diff --git a/packages/SettingsLib/LayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml index 6a2b729ff9eb..4a99e845a5fc 100644 --- a/packages/SettingsLib/LayoutPreference/res/values/styles.xml +++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml @@ -37,4 +37,35 @@ <item name="android:singleLine">true</item> <item name="android:ellipsize">marquee</item> </style> + + <style name="CrossProfileEntityHeaderIcon"> + <item name="android:layout_width">48dp</item> + <item name="android:layout_height">48dp</item> + <item name="android:layout_gravity">center</item> + <item name="android:gravity">center</item> + <item name="android:scaleType">fitCenter</item> + <item name="android:antialias">true</item> + </style> + + <style name="CrossProfileSwapHorizIcon"> + <item name="android:layout_width">24dp</item> + <item name="android:layout_height">24dp</item> + <item name="android:layout_gravity">center</item> + <item name="android:gravity">center</item> + <item name="android:scaleType">fitCenter</item> + <item name="android:layout_marginStart">10dp</item> + <item name="android:layout_marginEnd">10dp</item> + <item name="android:src">@drawable/ic_swap_horiz_grey</item> + <item name="android:antialias">true</item> + </style> + + <style name="CrossProfileEntityHeaderTitle"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_gravity">center</item> + <item name="android:textSize">18sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:fontFamily">google-sans-medium</item> + <item name="android:layout_marginTop">8dp</item> + </style> </resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp new file mode 100644 index 000000000000..ef592527ba92 --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/Android.bp @@ -0,0 +1,12 @@ +android_library { + name: "SettingsLibSchedulesProvider", + + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.annotation_annotation", + ], + + sdk_version: "system_current", + min_sdk_version: "21", +} diff --git a/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml new file mode 100644 index 000000000000..1b0e4bfc1e1d --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.schedulesprovider"> + + <uses-sdk android:minSdkVersion="21" /> + +</manifest> diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java new file mode 100644 index 000000000000..7d2b8e2878d6 --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/ScheduleInfo.java @@ -0,0 +1,167 @@ +/* + * 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.settingslib.schedulesprovider; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +/** + * This is a schedule data item. It contains the schedule title text, the summary text which + * displays on the summary of the Settings preference and an {@link Intent}. Intent is able to + * launch the editing page of the schedule data when user clicks this item (preference). + */ +public class ScheduleInfo implements Parcelable { + private static final String TAG = "ScheduleInfo"; + private final String mTitle; + private final String mSummary; + private final Intent mIntent; + + public ScheduleInfo(Builder builder) { + mTitle = builder.mTitle; + mSummary = builder.mSummary; + mIntent = builder.mIntent; + } + + protected ScheduleInfo(Parcel in) { + mTitle = in.readString(); + mSummary = in.readString(); + mIntent = in.readParcelable(Intent.class.getClassLoader()); + } + + /** + * Returns the title text. + * + * @return The title. + */ + public String getTitle() { + return mTitle; + } + + /** + * Returns the summary text. + * + * @return The summary. + */ + public String getSummary() { + return mSummary; + } + + /** + * Returns an {@link Intent}. + */ + public Intent getIntent() { + return mIntent; + } + + /** + * Verify the member variables are valid. + * + * @return {@code true} if all member variables are valid. + */ + public boolean isValid() { + return !TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mSummary) && (mIntent != null); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mTitle); + dest.writeString(mSummary); + dest.writeParcelable(mIntent, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<ScheduleInfo> CREATOR = new Creator<ScheduleInfo>() { + @Override + public ScheduleInfo createFromParcel(Parcel in) { + return new ScheduleInfo(in); + } + + @Override + public ScheduleInfo[] newArray(int size) { + return new ScheduleInfo[size]; + } + }; + + @NonNull + @Override + public String toString() { + return "title : " + mTitle + " summary : " + mSummary + (mIntent == null + ? " and intent is null." : "."); + } + + /** + * A simple builder for {@link ScheduleInfo}. + */ + public static class Builder { + @NonNull + private String mTitle; + @NonNull + private String mSummary; + @NonNull + private Intent mIntent; + + /** + * Sets the title. + * + * @param title The title of the preference item. + * @return This instance. + */ + public Builder setTitle(@NonNull String title) { + mTitle = title; + return this; + } + + /** + * Sets the summary. + * + * @param summary The summary of the preference summary. + * @return This instance. + */ + public Builder setSummary(@NonNull String summary) { + mSummary = summary; + return this; + } + + /** + * Sets the {@link Intent}. + * + * @param intent The action when user clicks the preference. + * @return This instance. + */ + public Builder setIntent(@NonNull Intent intent) { + mIntent = intent; + return this; + } + + /** + * Creates an instance of {@link ScheduleInfo}. + * + * @return The instance of {@link ScheduleInfo}. + */ + public ScheduleInfo build() { + return new ScheduleInfo(this); + } + } +} diff --git a/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java new file mode 100644 index 000000000000..a423e475d357 --- /dev/null +++ b/packages/SettingsLib/SchedulesProvider/src/com/android/settingslib/schedulesprovider/SchedulesProvider.java @@ -0,0 +1,133 @@ +/* + * 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.settingslib.schedulesprovider; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.SystemProperties; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * This provider is a bridge for client apps to provide the schedule data. + * Client provider needs to implement their {@link #getScheduleInfoList()} and returns a list of + * {@link ScheduleInfo}. + */ +public abstract class SchedulesProvider extends ContentProvider { + public static final String METHOD_GENERATE_SCHEDULE_INFO_LIST = "generateScheduleInfoList"; + public static final String BUNDLE_SCHEDULE_INFO_LIST = "scheduleInfoList"; + private static final String TAG = "SchedulesProvider"; + + @Override + public boolean onCreate() { + return true; + } + + @Override + public final Cursor query( + Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + throw new UnsupportedOperationException("Query operation is not supported currently."); + } + + @Override + public final String getType(Uri uri) { + throw new UnsupportedOperationException("GetType operation is not supported currently."); + } + + @Override + public final Uri insert(Uri uri, ContentValues values) { + throw new UnsupportedOperationException("Insert operation is not supported currently."); + } + + @Override + public final int delete(Uri uri, String selection, String[] selectionArgs) { + throw new UnsupportedOperationException("Delete operation not supported currently."); + } + + @Override + public final int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + throw new UnsupportedOperationException("Update operation is not supported currently."); + } + + /** + * Return the list of the schedule information. + * + * @return a list of the {@link ScheduleInfo}. + */ + public abstract ArrayList<ScheduleInfo> getScheduleInfoList(); + + /** + * Returns a bundle which contains a list of {@link ScheduleInfo} and data types: + * scheduleInfoList : ArrayList<ScheduleInfo> + */ + @Override + public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) { + final Bundle bundle = new Bundle(); + if (METHOD_GENERATE_SCHEDULE_INFO_LIST.equals(method)) { + final ArrayList<ScheduleInfo> scheduleInfoList = filterInvalidData( + getScheduleInfoList()); + if (scheduleInfoList != null) { + bundle.putParcelableArrayList(BUNDLE_SCHEDULE_INFO_LIST, scheduleInfoList); + } + } + return bundle; + } + + /** + * To filter the invalid schedule info. + * + * @param scheduleInfoList The list of the {@link ScheduleInfo}. + * @return The valid list of the {@link ScheduleInfo}. + */ + private ArrayList<ScheduleInfo> filterInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) { + if (scheduleInfoList == null) { + Log.d(TAG, "package : " + getContext().getPackageName() + " has no scheduling data."); + return null; + } + // Dump invalid data in debug mode. + if (SystemProperties.getInt("ro.debuggable", 0) == 1) { + new Thread(() -> { + dumpInvalidData(scheduleInfoList); + }).start(); + } + final List<ScheduleInfo> filteredList = scheduleInfoList + .stream() + .filter(scheduleInfo -> scheduleInfo.isValid()) + .collect(Collectors.toList()); + + return new ArrayList<>(filteredList); + } + + private void dumpInvalidData(ArrayList<ScheduleInfo> scheduleInfoList) { + Log.d(TAG, "package : " + getContext().getPackageName() + + " provided some scheduling data are invalid."); + scheduleInfoList + .stream() + .filter(scheduleInfo -> !scheduleInfo.isValid()) + .forEach(scheduleInfo -> Log.d(TAG, scheduleInfo.toString())); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index e91096760180..8e4a982fde8c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -74,13 +74,6 @@ public class InfoMediaManager extends MediaManager { refreshDevices(); } - @VisibleForTesting - String getControlCategoryByPackageName(String packageName) { - //TODO(b/117129183): Use package name to get ControlCategory. - //Since api not ready, return fixed ControlCategory for prototype. - return "com.google.android.gms.cast.CATEGORY_CAST"; - } - @Override public void stopScan() { mRouterManager.unregisterCallback(mMediaRouterCallback); @@ -192,5 +185,10 @@ public class InfoMediaManager extends MediaManager { public void onRoutesChanged(List<MediaRoute2Info> routes) { refreshDevices(); } + + @Override + public void onRoutesRemoved(List<MediaRoute2Info> routes) { + refreshDevices(); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 984ab11a1a71..f8db70ae0963 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -16,6 +16,8 @@ package com.android.settingslib.media; import android.app.Notification; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.text.TextUtils; @@ -26,13 +28,13 @@ import androidx.annotation.IntDef; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -66,9 +68,16 @@ public class LocalMediaManager implements BluetoothCallback { @VisibleForTesting List<MediaDevice> mMediaDevices = new ArrayList<>(); @VisibleForTesting + List<MediaDevice> mDisconnectedMediaDevices = new ArrayList<>(); + @VisibleForTesting MediaDevice mPhoneDevice; @VisibleForTesting MediaDevice mCurrentConnectedDevice; + @VisibleForTesting + DeviceAttributeChangeCallback mDeviceAttributeChangeCallback = + new DeviceAttributeChangeCallback(); + @VisibleForTesting + BluetoothAdapter mBluetoothAdapter; /** * Register to start receiving callbacks for MediaDevice events. @@ -89,6 +98,7 @@ public class LocalMediaManager implements BluetoothCallback { mPackageName = packageName; mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mLocalBluetoothManager == null) { Log.e(TAG, "Bluetooth is not supported on this device"); return; @@ -164,7 +174,7 @@ public class LocalMediaManager implements BluetoothCallback { } void dispatchDeviceListUpdate() { - Collections.sort(mMediaDevices, COMPARATOR); + //TODO(b/149260820): Use new rule to rank device once device type api is ready. for (DeviceCallback callback : getCallbacks()) { callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices)); } @@ -278,12 +288,44 @@ public class LocalMediaManager implements BluetoothCallback { public void onDeviceListAdded(List<MediaDevice> devices) { mMediaDevices.clear(); mMediaDevices.addAll(devices); + mMediaDevices.addAll(buildDisconnectedBluetoothDevice()); + final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice(); mCurrentConnectedDevice = infoMediaDevice != null ? infoMediaDevice : updateCurrentConnectedDevice(); dispatchDeviceListUpdate(); } + private List<MediaDevice> buildDisconnectedBluetoothDevice() { + for (MediaDevice device : mDisconnectedMediaDevices) { + ((BluetoothMediaDevice) device).getCachedDevice() + .unregisterCallback(mDeviceAttributeChangeCallback); + } + mDisconnectedMediaDevices.clear(); + final List<BluetoothDevice> bluetoothDevices = + mBluetoothAdapter.getMostRecentlyConnectedDevices(); + final CachedBluetoothDeviceManager cachedDeviceManager = + mLocalBluetoothManager.getCachedDeviceManager(); + + for (BluetoothDevice device : bluetoothDevices) { + final CachedBluetoothDevice cachedDevice = + cachedDeviceManager.findDevice(device); + if (cachedDevice != null) { + if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED + && !cachedDevice.isConnected()) { + final MediaDevice mediaDevice = new BluetoothMediaDevice(mContext, + cachedDevice, + null, null, mPackageName); + if (!mMediaDevices.contains(mediaDevice)) { + cachedDevice.registerCallback(mDeviceAttributeChangeCallback); + mDisconnectedMediaDevices.add(mediaDevice); + } + } + } + } + return new ArrayList<>(mDisconnectedMediaDevices); + } + @Override public void onDeviceRemoved(MediaDevice device) { if (mMediaDevices.contains(device)) { @@ -345,4 +387,17 @@ public class LocalMediaManager implements BluetoothCallback { */ default void onDeviceAttributesChanged() {}; } + + /** + * This callback is for update {@link BluetoothMediaDevice} summary when + * {@link CachedBluetoothDevice} connection state is changed. + */ + @VisibleForTesting + class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback { + + @Override + public void onDeviceAttributesChanged() { + dispatchDeviceAttributesChanged(); + } + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 05f5fa0afc9e..8b815bfba19d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -203,4 +203,46 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse(); } + + @Test + public void onRoutesRemoved_getAvailableRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } + + @Test + public void onRoutesRemoved_buildAllRoutes_shouldAddMediaDevice() { + final MediaRoute2Info info = mock(MediaRoute2Info.class); + when(info.getId()).thenReturn(TEST_ID); + when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + final List<MediaRoute2Info> routes = new ArrayList<>(); + routes.add(info); + when(mRouterManager.getAllRoutes()).thenReturn(routes); + + final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID); + assertThat(mediaDevice).isNull(); + + mInfoMediaManager.mPackageName = ""; + mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes); + + final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0); + assertThat(infoDevice.getId()).isEqualTo(TEST_ID); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size()); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 3d67ba053a45..3611dfefbb7f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -25,13 +25,17 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.content.Context; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; import org.junit.Before; import org.junit.Test; @@ -40,11 +44,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) public class LocalMediaManagerTest { private static final String TEST_DEVICE_ID_1 = "device_id_1"; @@ -69,11 +76,15 @@ public class LocalMediaManagerTest { private Context mContext; private LocalMediaManager mLocalMediaManager; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + final List<BluetoothDevice> bluetoothDevices = new ArrayList<>(); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices); when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager); when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); @@ -81,6 +92,7 @@ public class LocalMediaManagerTest { mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager, mInfoMediaManager, "com.test.packagename"); + mLocalMediaManager.mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } @Test @@ -419,4 +431,50 @@ public class LocalMediaManagerTest { MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); assertThat(activeDevices).containsExactly(device3); } + + @Test + public void onDeviceAttributesChanged_shouldBeCalled() { + mLocalMediaManager.registerCallback(mCallback); + + mLocalMediaManager.mDeviceAttributeChangeCallback.onDeviceAttributesChanged(); + + verify(mCallback).onDeviceAttributesChanged(); + } + + @Test + public void onDeviceListAdded_haveDisconnectedDevice_addDisconnectedDevice() { + final List<MediaDevice> devices = new ArrayList<>(); + final MediaDevice device1 = mock(MediaDevice.class); + final MediaDevice device2 = mock(MediaDevice.class); + final MediaDevice device3 = mock(MediaDevice.class); + mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class); + devices.add(device1); + devices.add(device2); + mLocalMediaManager.mMediaDevices.add(device3); + mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice); + + final List<BluetoothDevice> bluetoothDevices = new ArrayList<>(); + final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class); + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class); + bluetoothDevices.add(bluetoothDevice); + mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices); + + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager); + when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice); + when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(cachedDevice.isConnected()).thenReturn(false); + + when(device1.getId()).thenReturn(TEST_DEVICE_ID_1); + when(device2.getId()).thenReturn(TEST_DEVICE_ID_2); + when(device3.getId()).thenReturn(TEST_DEVICE_ID_3); + when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id"); + + assertThat(mLocalMediaManager.mMediaDevices).hasSize(2); + mLocalMediaManager.registerCallback(mCallback); + mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices); + + assertThat(mLocalMediaManager.mMediaDevices).hasSize(3); + verify(mCallback).onDeviceListUpdate(any()); + } } diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java index 906dba487734..015ce149a9c4 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java @@ -17,6 +17,7 @@ package com.android.settingslib.testutils.shadow; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; @@ -29,6 +30,7 @@ import java.util.List; public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBluetoothAdapter { private List<Integer> mSupportedProfiles; + private List<BluetoothDevice> mMostRecentlyConnectedDevices; private BluetoothProfile.ServiceListener mServiceListener; @Implementation @@ -50,4 +52,13 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto public void setSupportedProfiles(List<Integer> supportedProfiles) { mSupportedProfiles = supportedProfiles; } + + @Implementation + protected List<BluetoothDevice> getMostRecentlyConnectedDevices() { + return mMostRecentlyConnectedDevices; + } + + public void setMostRecentlyConnectedDevices(List<BluetoothDevice> list) { + mMostRecentlyConnectedDevices = list; + } } diff --git a/packages/SettingsProvider/res/values/strings.xml b/packages/SettingsProvider/res/values/strings.xml index 378772750194..76bea3160afe 100644 --- a/packages/SettingsProvider/res/values/strings.xml +++ b/packages/SettingsProvider/res/values/strings.xml @@ -23,15 +23,10 @@ <!-- A notification is shown when the user's softap config has been changed due to underlying hardware restrictions. This is the notifications's title. [CHAR_LIMIT=NONE] --> - <string name="wifi_softap_config_change">Changes to your hotspot settings</string> + <string name="wifi_softap_config_change">Hotspot settings have changed</string> <!-- A notification is shown when the user's softap config has been changed due to underlying hardware restrictions. This is the notification's summary message. [CHAR_LIMIT=NONE] --> - <string name="wifi_softap_config_change_summary">Your hotspot band has changed.</string> - - <!-- A notification is shown when the user's softap config has been changed due to underlying - hardware restrictions. This is the notification's full message. - [CHAR_LIMIT=NONE] --> - <string name="wifi_softap_config_change_detailed">This device doesn\u2019t support your preference for 5GHz only. Instead, this device will use the 5GHz band when available.</string> + <string name="wifi_softap_config_change_summary">Tap to see details</string> </resources> diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 8037266ac92f..c5d4fa9f1b40 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -126,6 +126,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.SCREEN_OFF_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(System.SCREEN_BRIGHTNESS_FOR_VR, new InclusiveIntegerRangeValidator(0, 255)); VALIDATORS.put(System.SCREEN_BRIGHTNESS_MODE, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR); VALIDATORS.put(System.MODE_RINGER_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(System.MUTE_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(System.VIBRATE_ON, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java index 1ee5f9093421..ca841a5cdcd6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java @@ -57,7 +57,6 @@ public class WifiSoftApConfigChangedNotifier { Resources resources = context.getResources(); CharSequence title = resources.getText(R.string.wifi_softap_config_change); CharSequence contentSummary = resources.getText(R.string.wifi_softap_config_change_summary); - CharSequence content = resources.getText(R.string.wifi_softap_config_change_detailed); int color = resources.getColor( android.R.color.system_notification_accent_color, context.getTheme()); @@ -73,7 +72,6 @@ public class WifiSoftApConfigChangedNotifier { .setLocalOnly(true) .setColor(color) .setStyle(new Notification.BigTextStyle() - .bigText(content) .setBigContentTitle(title) .setSummaryText(contentSummary)) .setAutoCancel(true) diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index cc2c92b4c7eb..d821050ac5b4 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -253,7 +253,8 @@ <!-- Permission required for CTS test - ShortcutManagerUsageTest --> <uses-permission android:name="android.permission.ACCESS_SHORTCUTS"/> - <!-- Permission required for CTS test - UsageStatsTest --> + <!-- Permissions required for CTS test - UsageStatsTest --> + <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS"/> <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"/> <!-- Permissions required to test ambient display. --> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 18145939c384..530823a6a206 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -606,26 +606,21 @@ public class BugreportProgressService extends Service { BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle, shareDescription, bugreportType, mBugreportsDir); - ParcelFileDescriptor bugreportFd; - ParcelFileDescriptor screenshotFd; - - try { - bugreportFd = info.createAndGetBugreportFd(); - if (bugreportFd == null) { - Log.e(TAG, "Bugreport parcel file descriptor is null."); - return; - } - screenshotFd = info.createAndGetDefaultScreenshotFd(); - if (screenshotFd == null) { - Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file"); - FileUtils.closeQuietly(bugreportFd); - info.bugreportFile.delete(); - return; - } - } catch (IOException e) { - Log.e(TAG, "Error in generating bugreport files: ", e); + ParcelFileDescriptor bugreportFd = info.getBugreportFd(); + if (bugreportFd == null) { + Log.e(TAG, "Failed to start bugreport generation as " + + " bugreport parcel file descriptor is null."); return; } + ParcelFileDescriptor screenshotFd = info.getDefaultScreenshotFd(); + if (screenshotFd == null) { + Log.e(TAG, "Failed to start bugreport generation as" + + " screenshot parcel file descriptor is null. Deleting bugreport file"); + FileUtils.closeQuietly(bugreportFd); + info.bugreportFile.delete(); + return; + } + mBugreportManager = (BugreportManager) mContext.getSystemService( Context.BUGREPORT_SERVICE); final Executor executor = ActivityThread.currentActivityThread().getExecutor(); @@ -639,7 +634,7 @@ public class BugreportProgressService extends Service { mBugreportManager.startBugreport(bugreportFd, screenshotFd, new BugreportParams(bugreportType), executor, bugreportCallback); } catch (RuntimeException e) { - Log.i(TAG, "error in generating bugreports: ", e); + Log.i(TAG, "Error in generating bugreports: ", e); // The binder call didn't go through successfully, so need to close the fds. // If the calls went through API takes ownership. FileUtils.closeQuietly(bugreportFd); @@ -657,11 +652,15 @@ public class BugreportProgressService extends Service { return null; } - private static void createReadWriteFile(File file) throws IOException { - if (!file.exists()) { - file.createNewFile(); - file.setReadable(true, true); - file.setWritable(true, true); + private static void createReadWriteFile(File file) { + try { + if (!file.exists()) { + file.createNewFile(); + file.setReadable(true, true); + file.setWritable(true, true); + } + } catch (IOException e) { + Log.e(TAG, "Error in creating bugreport file: ", e); } } @@ -1836,23 +1835,23 @@ public class BugreportProgressService extends Service { void createBugreportFile(File bugreportsDir) { bugreportFile = new File(bugreportsDir, getFileName(this, ".zip")); + createReadWriteFile(bugreportFile); } void createScreenshotFile(File bugreportsDir) { File screenshotFile = new File(bugreportsDir, getScreenshotName("default")); addScreenshot(screenshotFile); + createReadWriteFile(screenshotFile); } - ParcelFileDescriptor createAndGetBugreportFd() throws IOException { - createReadWriteFile(bugreportFile); + ParcelFileDescriptor getBugreportFd() { return getFd(bugreportFile); } - ParcelFileDescriptor createAndGetDefaultScreenshotFd() throws IOException { + ParcelFileDescriptor getDefaultScreenshotFd() { if (screenshotFiles.isEmpty()) { return null; } - createReadWriteFile(screenshotFiles.get(0)); return getFd(screenshotFiles.get(0)); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index 68b05e358786..9de10406a822 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -16,6 +16,7 @@ package com.android.systemui.biometrics; +import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricPrompt; @@ -33,6 +34,8 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -126,18 +129,18 @@ public abstract class AuthCredentialView extends LinearLayout { mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS); } - private void setTextOrHide(TextView view, String string) { - if (TextUtils.isEmpty(string)) { + private void setTextOrHide(TextView view, CharSequence text) { + if (TextUtils.isEmpty(text)) { view.setVisibility(View.GONE); } else { - view.setText(string); + view.setText(text); } Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); } - private void setText(TextView view, String string) { - view.setText(string); + private void setText(TextView view, CharSequence text) { + view.setText(text); } void setEffectiveUserId(int effectiveUserId) { @@ -173,11 +176,9 @@ public abstract class AuthCredentialView extends LinearLayout { protected void onAttachedToWindow() { super.onAttachedToWindow(); - setText(mTitleView, mBiometricPromptBundle.getString(BiometricPrompt.KEY_TITLE)); - setTextOrHide(mSubtitleView, - mBiometricPromptBundle.getString(BiometricPrompt.KEY_SUBTITLE)); - setTextOrHide(mDescriptionView, - mBiometricPromptBundle.getString(BiometricPrompt.KEY_DESCRIPTION)); + setText(mTitleView, getTitle(mBiometricPromptBundle)); + setTextOrHide(mSubtitleView, getSubtitle(mBiometricPromptBundle)); + setTextOrHide(mDescriptionView, getDescription(mBiometricPromptBundle)); final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId); final Drawable image; @@ -279,4 +280,28 @@ public abstract class AuthCredentialView extends LinearLayout { } } } + + @Nullable + private static CharSequence getTitle(@NonNull Bundle bundle) { + final CharSequence credentialTitle = + bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE); + return credentialTitle != null ? credentialTitle + : bundle.getCharSequence(BiometricPrompt.KEY_TITLE); + } + + @Nullable + private static CharSequence getSubtitle(@NonNull Bundle bundle) { + final CharSequence credentialSubtitle = + bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE); + return credentialSubtitle != null ? credentialSubtitle + : bundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); + } + + @Nullable + private static CharSequence getDescription(@NonNull Bundle bundle) { + final CharSequence credentialDescription = + bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION); + return credentialDescription != null ? credentialDescription + : bundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index d298b992d418..56cfe8010fef 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -64,12 +64,12 @@ import android.util.ArraySet; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.ContextThemeWrapper; +import android.view.IWindowManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.ImageView; @@ -88,13 +88,13 @@ import com.android.internal.util.ScreenRecordHelper; import com.android.internal.util.ScreenshotHelper; import com.android.internal.view.RotationPolicy; import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.controls.ui.ControlsUiController; +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.GlobalActions.GlobalActionsManager; @@ -110,6 +110,7 @@ import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolat import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -155,6 +156,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final BroadcastDispatcher mBroadcastDispatcher; private final ContentResolver mContentResolver; private final Resources mResources; + private final ConfigurationController mConfigurationController; private final UserManager mUserManager; private final TrustManager mTrustManager; private final IActivityManager mIActivityManager; @@ -186,6 +188,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final NotificationShadeWindowController mNotificationShadeWindowController; private GlobalActionsPanelPlugin mPanelPlugin; private ControlsUiController mControlsUiController; + private final IWindowManager mIWindowManager; + private final Executor mBackgroundExecutor; /** * @param context everything needs a context :( @@ -204,7 +208,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, BlurUtils blurUtils, SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, - ControlsUiController controlsUiController) { + ControlsUiController controlsUiController, IWindowManager iWindowManager, + @Background Executor backgroundExecutor) { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -215,6 +220,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mBroadcastDispatcher = broadcastDispatcher; mContentResolver = contentResolver; mResources = resources; + mConfigurationController = configurationController; mUserManager = userManager; mTrustManager = trustManager; mIActivityManager = iActivityManager; @@ -225,6 +231,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; mControlsUiController = controlsUiController; + mIWindowManager = iWindowManager; + mBackgroundExecutor = backgroundExecutor; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -249,7 +257,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mScreenshotHelper = new ScreenshotHelper(context); mScreenRecordHelper = new ScreenRecordHelper(context); - configurationController.addCallback(this); + mConfigurationController.addCallback(this); mActivityStarter = activityStarter; keyguardStateController.addCallback(new KeyguardStateController.Callback() { @@ -495,7 +503,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } public void destroy() { - Dependency.get(ConfigurationController.class).removeCallback(this); + mConfigurationController.removeCallback(this); } private final class PowerAction extends SinglePressAction implements LongPressAction { @@ -836,14 +844,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @Override public void onPress() { - new LockPatternUtils(mContext) - .requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, - UserHandle.USER_ALL); + mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, + UserHandle.USER_ALL); try { - WindowManagerGlobal.getWindowManagerService().lockNow(null); + mIWindowManager.lockNow(null); // Lock profiles (if any) on the background thread. - final Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - bgHandler.post(() -> lockProfiles()); + mBackgroundExecutor.execute(() -> lockProfiles()); } catch (RemoteException e) { Log.e(TAG, "Error while trying to lock device.", e); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index ab8de263765e..5c1d332df7a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -105,8 +105,8 @@ class QSLogger @Inject constructor( fun logTileUpdated(tileSpec: String, state: QSTile.State) { log(VERBOSE, { str1 = tileSpec - str2 = state.label.toString() - str3 = state.icon.toString() + str2 = state.label?.toString() + str3 = state.icon?.toString() int1 = state.state if (state is QSTile.SignalState) { bool1 = true diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 573ea4dd85de..9f64b397e9d9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -352,6 +352,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis try { Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, + AccessibilityManager.ACCESSIBILITY_BUTTON); mContext.startActivityAsUser(intent, UserHandle.CURRENT); } finally { Binder.restoreCallingIdentity(token); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index 4f27c0f04c3f..5b4a927bb8f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -26,26 +26,23 @@ import android.content.Context import android.content.DialogInterface import android.graphics.Color import android.graphics.PixelFormat -import android.graphics.drawable.Drawable import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.util.Log import android.view.Gravity import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.Window -import android.view.WindowInsets.Type import android.view.WindowInsets.Type.statusBars import android.view.WindowManager import android.widget.TextView import com.android.internal.annotations.VisibleForTesting - import com.android.systemui.R - import javax.inject.Inject import javax.inject.Singleton -const val TAG = "ChannelDialogController" +private const val TAG = "ChannelDialogController" /** * ChannelEditorDialogController is the controller for the dialog half-shelf @@ -149,9 +146,9 @@ class ChannelEditorDialogController @Inject constructor( val channels = groupList .flatMap { group -> group.channels.asSequence().filterNot { channel -> - channel.isImportanceLockedByOEM - || channel.importance == IMPORTANCE_NONE - || channel.isImportanceLockedByCriticalDeviceFunction + channel.isImportanceLockedByOEM || + channel.importance == IMPORTANCE_NONE || + channel.isImportanceLockedByCriticalDeviceFunction } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index e2513dac44d8..d744fc398d7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -74,11 +74,15 @@ import javax.inject.Singleton; @Singleton public final class NotifBindPipeline { private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>(); + private final NotifBindPipelineLogger mLogger; private BindStage mStage; @Inject - NotifBindPipeline(CommonNotifCollection collection) { + NotifBindPipeline( + CommonNotifCollection collection, + NotifBindPipelineLogger logger) { collection.addCollectionListener(mCollectionListener); + mLogger = logger; } /** @@ -86,6 +90,8 @@ public final class NotifBindPipeline { */ public void setStage( BindStage stage) { + mLogger.logStageSet(stage.getClass().getName()); + mStage = stage; mStage.setBindRequestListener(this::onBindRequested); } @@ -96,6 +102,8 @@ public final class NotifBindPipeline { public void manageRow( @NonNull NotificationEntry entry, @NonNull ExpandableNotificationRow row) { + mLogger.logManagedRow(entry.getKey()); + final BindEntry bindEntry = getBindEntry(entry); bindEntry.row = row; if (bindEntry.invalidated) { @@ -130,6 +138,8 @@ public final class NotifBindPipeline { * callbacks when the run finishes. If a run is already in progress, it is restarted. */ private void startPipeline(NotificationEntry entry) { + mLogger.logStartPipeline(entry.getKey()); + if (mStage == null) { throw new IllegalStateException("No stage was ever set on the pipeline"); } @@ -147,10 +157,11 @@ public final class NotifBindPipeline { private void onPipelineComplete(NotificationEntry entry) { final BindEntry bindEntry = getBindEntry(entry); + final Set<BindCallback> callbacks = bindEntry.callbacks; - bindEntry.invalidated = false; + mLogger.logFinishedPipeline(entry.getKey(), callbacks.size()); - final Set<BindCallback> callbacks = bindEntry.callbacks; + bindEntry.invalidated = false; for (BindCallback cb : callbacks) { cb.onBindFinished(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt new file mode 100644 index 000000000000..2717d7ad143b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -0,0 +1,61 @@ +/* + * 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.systemui.statusbar.notification.row + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class NotifBindPipelineLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logStageSet(stageName: String) { + buffer.log(TAG, INFO, { + str1 = stageName + }, { + "Stage set: $str1" + }) + } + + fun logManagedRow(notifKey: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + }, { + "Row set for notif: $str1" + }) + } + + fun logStartPipeline(notifKey: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + }, { + "Start pipeline for notif: $str1" + }) + } + + fun logFinishedPipeline(notifKey: String, numCallbacks: Int) { + buffer.log(TAG, INFO, { + str1 = notifKey + int1 = numCallbacks + }, { + "Finished pipeline for notif $str1 with $int1 callbacks" + }) + } +} + +private const val TAG = "NotifBindPipeline"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java index 5170d0b85b17..88ed0bb37d0d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java @@ -157,6 +157,15 @@ public final class RowContentBindParams { return mViewsNeedReinflation; } + @Override + public String toString() { + return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x " + + "mUseLowPriority=%b mUseChildInGroup=%b mUseIncreasedHeight=%b " + + "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]", + mContentViews, mDirtyContentViews, mUseLowPriority, mUseChildInGroup, + mUseIncreasedHeight, mUseIncreasedHeadsUpHeight, mViewsNeedReinflation); + } + /** * Content views that should be inflated by default for all notifications. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java index f78324596fb4..c632f3eb22a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -38,13 +38,16 @@ import javax.inject.Singleton; public class RowContentBindStage extends BindStage<RowContentBindParams> { private final NotificationRowContentBinder mBinder; private final NotifInflationErrorManager mNotifInflationErrorManager; + private final RowContentBindStageLogger mLogger; @Inject RowContentBindStage( NotificationRowContentBinder binder, - NotifInflationErrorManager errorManager) { + NotifInflationErrorManager errorManager, + RowContentBindStageLogger logger) { mBinder = binder; mNotifInflationErrorManager = errorManager; + mLogger = logger; } @Override @@ -54,6 +57,8 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> { @NonNull StageCallback callback) { RowContentBindParams params = getStageParams(entry); + mLogger.logStageParams(entry.getKey(), params.toString()); + // Resolve content to bind/unbind. @InflationFlag int inflationFlags = params.getContentViews(); @InflationFlag int invalidatedFlags = params.getDirtyContentViews(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt new file mode 100644 index 000000000000..29cce3375c8a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt @@ -0,0 +1,37 @@ +/* + * 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.systemui.statusbar.notification.row + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class RowContentBindStageLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logStageParams(notifKey: String, stageParams: String) { + buffer.log(TAG, INFO, { + str1 = notifKey + str2 = stageParams + }, { + "Invalidated notif $str1 with params: \n$str2" + }) + } +} + +private const val TAG = "RowContentBindStage"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 3f5215e1a639..fd8c71b48417 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -956,6 +956,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback private boolean onAccessibilityLongClick(View v) { Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, + AccessibilityManager.ACCESSIBILITY_BUTTON); v.getContext().startActivityAsUser(intent, UserHandle.CURRENT); return true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 5a0a495e1f85..f1fba792a882 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -282,6 +282,9 @@ public class NotificationEntryManagerTest extends SysuiTestCase { .inflationCallback(any())) .thenReturn(mExpandableNotificationRowComponentBuilder); when(mExpandableNotificationRowComponentBuilder + .rowContentBindStage(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder .onExpandClickListener(any())) .thenReturn(mExpandableNotificationRowComponentBuilder); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java index 408bba48d422..6408f7a38133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -59,7 +59,7 @@ public class NotifBindPipelineTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection); + mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); mBindPipeline.setStage(mStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = 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 fd5512d62968..7a1bd052a336 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 @@ -111,11 +111,13 @@ public class NotificationTestHelper { mock(NotifRemoteViewCache.class), mock(NotificationRemoteInputManager.class)); contentBinder.setInflateSynchronously(true); - mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class)); + mBindStage = new RowContentBindStage(contentBinder, + mock(NotifInflationErrorManager.class), + mock(RowContentBindStageLogger.class)); CommonNotifCollection collection = mock(CommonNotifCollection.class); - mBindPipeline = new NotifBindPipeline(collection); + mBindPipeline = new NotifBindPipeline(collection, mock(NotifBindPipelineLogger.class)); mBindPipeline.setStage(mBindStage); ArgumentCaptor<NotifCollectionListener> collectionListenerCaptor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index d9fe6551ba1c..0f2482ce9c4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -60,8 +60,10 @@ public class RowContentBindStageTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mRowContentBindStage = new RowContentBindStage(mBinder, - mock(NotifInflationErrorManager.class)); + mRowContentBindStage = new RowContentBindStage( + mBinder, + mock(NotifInflationErrorManager.class), + mock(RowContentBindStageLogger.class)); mRowContentBindStage.createStageParams(mEntry); } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 75ec4b04eeed..6c8aaf465c44 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -16,8 +16,9 @@ package com.android.server.accessibility; -import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE; import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER; +import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP; import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; @@ -39,6 +40,7 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.graphics.GraphicBuffer; +import android.graphics.ParcelableColorSpace; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -1021,13 +1023,15 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final GraphicBuffer graphicBuffer = screenshotBuffer.getGraphicBuffer(); final HardwareBuffer hardwareBuffer = HardwareBuffer.createFromGraphicBuffer(graphicBuffer); - final int colorSpaceId = screenshotBuffer.getColorSpace().getId(); + final ParcelableColorSpace colorSpace = + new ParcelableColorSpace(screenshotBuffer.getColorSpace()); // Send back the result. final Bundle payload = new Bundle(); payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, hardwareBuffer); - payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE_ID, colorSpaceId); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace); + payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, SystemClock.uptimeMillis()); callback.sendResult(payload); }, null).recycleOnUse()); } finally { diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index b74be7e3f345..a3b5a3e2dcf9 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -17,6 +17,7 @@ package com.android.server.accessibility.gestures; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; @@ -24,6 +25,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT; @@ -31,6 +33,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT; @@ -132,6 +135,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( + new MultiFingerMultiTapAndHold( + mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this)); + mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this)); // Three-finger taps. mMultiFingerGestures.add( @@ -139,6 +145,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( + new MultiFingerMultiTapAndHold( + mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, this)); + mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this)); // Four-finger taps. mMultiFingerGestures.add( @@ -146,6 +155,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( + new MultiFingerMultiTapAndHold( + mContext, 4, 2, GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD, this)); + mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this)); // Two-finger swipes. mMultiFingerGestures.add( diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java index 20def6154977..e5340f10dc4c 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java @@ -42,10 +42,10 @@ class MultiFingerMultiTap extends GestureMatcher { // The acceptable distance the pointer can move and still count as a tap. private int mTouchSlop; // A tap counts when target number of fingers are down and up once. - private int mCompletedTapCount; + protected int mCompletedTapCount; // A flag set to true when target number of fingers have touched down at once before. // Used to indicate what next finger action should be. Down when false and lift when true. - private boolean mIsTargetFingerCountReached = false; + protected boolean mIsTargetFingerCountReached = false; // Store initial down points for slop checking and update when next down if is inside slop. private PointF[] mBases; // The points in bases that already have slop checked when onDown or onPointerDown. @@ -56,7 +56,11 @@ class MultiFingerMultiTap extends GestureMatcher { * @throws IllegalArgumentException if <code>fingers<code/> is less than 2 * or <code>taps<code/> is not positive. */ - MultiFingerMultiTap(Context context, int fingers, int taps, int gestureId, + MultiFingerMultiTap( + Context context, + int fingers, + int taps, + int gestureId, GestureMatcher.StateChangeListener listener) { super(gestureId, new Handler(context.getMainLooper()), listener); Preconditions.checkArgument(fingers >= 2); @@ -117,8 +121,7 @@ class MultiFingerMultiTap extends GestureMatcher { cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags); final PointF nearest = findNearestPoint(rawEvent, mTouchSlop, false); - if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) - && null != nearest) { + if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && null != nearest) { // Increase current tap count when the user have all fingers lifted // within the tap timeout since the target number of fingers are down. if (mIsTargetFingerCountReached) { @@ -169,8 +172,7 @@ class MultiFingerMultiTap extends GestureMatcher { } else { nearest = findNearestPoint(rawEvent, mDoubleTapSlop, true); } - if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) - && nearest != null) { + if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) && nearest != null) { // The user have all fingers down within the tap timeout since first finger down, // setting the timeout for fingers to be lifted. if (currentFingerCount == mTargetFingerCount) { @@ -227,11 +229,11 @@ class MultiFingerMultiTap extends GestureMatcher { } /** - * Find the nearest location to the given event in the bases. - * If no one found, it could be not inside {@code slop}, filtered or empty bases. - * When {@code filterMatched} is true, if the location of given event matches one of the points - * in {@link #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location - * will be added to {@link #mExcludedPointsForDownSlopChecked}. + * Find the nearest location to the given event in the bases. If no one found, it could be not + * inside {@code slop}, filtered or empty bases. When {@code filterMatched} is true, if the + * location of given event matches one of the points in {@link + * #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location will be + * added to {@link #mExcludedPointsForDownSlopChecked}. * * @param event to find nearest point in bases. * @param slop to check to the given location of the event. diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java new file mode 100644 index 000000000000..7824fd902c9b --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java @@ -0,0 +1,71 @@ +/* + * 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.server.accessibility.gestures; + +import android.content.Context; +import android.view.MotionEvent; + +/** + * This class matches gestures of the form multi-finger multi-tap and hold. The number of fingers + * and taps for each instance is specified in the constructor. + */ +class MultiFingerMultiTapAndHold extends MultiFingerMultiTap { + + MultiFingerMultiTapAndHold( + Context context, + int fingers, + int taps, + int gestureId, + GestureMatcher.StateChangeListener listener) { + super(context, fingers, taps, gestureId, listener); + } + + @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + super.onPointerDown(event, rawEvent, policyFlags); + if (mIsTargetFingerCountReached && mCompletedTapCount + 1 == mTargetTapCount) { + completeAfterLongPressTimeout(event, rawEvent, policyFlags); + } + } + + @Override + protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + if (mCompletedTapCount + 1 == mTargetFingerCount) { + // Calling super.onUp would complete the multi-tap version of this. + cancelGesture(event, rawEvent, policyFlags); + } else { + super.onUp(event, rawEvent, policyFlags); + cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags); + } + } + + @Override + public String getGestureName() { + final StringBuilder builder = new StringBuilder(); + builder.append(mTargetFingerCount).append("-Finger "); + if (mTargetTapCount == 1) { + builder.append("Single"); + } else if (mTargetTapCount == 2) { + builder.append("Double"); + } else if (mTargetTapCount == 3) { + builder.append("Triple"); + } else if (mTargetTapCount > 3) { + builder.append(mTargetTapCount); + } + return builder.append(" Tap and hold").toString(); + } +} diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 4f49fb7578a1..0b3899d15993 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -16,6 +16,8 @@ package com.android.server.appprediction; +import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppGlobals; @@ -30,12 +32,17 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.provider.DeviceConfig; import android.service.appprediction.AppPredictionService; +import android.service.appprediction.IPredictionService; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.AbstractRemoteService; +import com.android.server.LocalServices; import com.android.server.infra.AbstractPerUserSystemService; +import com.android.server.people.PeopleServiceInternal; import java.util.function.Consumer; @@ -47,6 +54,8 @@ public class AppPredictionPerUserService extends implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks { private static final String TAG = AppPredictionPerUserService.class.getSimpleName(); + private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX = + "predict_using_people_service_"; @Nullable @GuardedBy("mLock") @@ -104,14 +113,16 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context, @NonNull AppPredictionSessionId sessionId) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.onCreatePredictionSession(context, sessionId); - - if (!mSessionInfos.containsKey(sessionId)) { - mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, - this::removeAppPredictionSessionInfo)); - } + if (!mSessionInfos.containsKey(sessionId)) { + mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, + DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, + PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false), + this::removeAppPredictionSessionInfo)); + } + final boolean serviceExists = resolveService(sessionId, s -> + s.onCreatePredictionSession(context, sessionId)); + if (!serviceExists) { + mSessionInfos.remove(sessionId); } } @@ -121,10 +132,7 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void notifyAppTargetEventLocked(@NonNull AppPredictionSessionId sessionId, @NonNull AppTargetEvent event) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.notifyAppTargetEvent(sessionId, event); - } + resolveService(sessionId, s -> s.notifyAppTargetEvent(sessionId, event)); } /** @@ -133,10 +141,8 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void notifyLaunchLocationShownLocked(@NonNull AppPredictionSessionId sessionId, @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.notifyLaunchLocationShown(sessionId, launchLocation, targetIds); - } + resolveService(sessionId, s -> + s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds)); } /** @@ -145,10 +151,7 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void sortAppTargetsLocked(@NonNull AppPredictionSessionId sessionId, @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.sortAppTargets(sessionId, targets, callback); - } + resolveService(sessionId, s -> s.sortAppTargets(sessionId, targets, callback)); } /** @@ -157,14 +160,11 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void registerPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.registerPredictionUpdates(sessionId, callback); - - AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); - if (sessionInfo != null) { - sessionInfo.addCallbackLocked(callback); - } + final boolean serviceExists = resolveService(sessionId, s -> + s.registerPredictionUpdates(sessionId, callback)); + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (serviceExists && sessionInfo != null) { + sessionInfo.addCallbackLocked(callback); } } @@ -174,14 +174,11 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") public void unregisterPredictionUpdatesLocked(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.unregisterPredictionUpdates(sessionId, callback); - - AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); - if (sessionInfo != null) { - sessionInfo.removeCallbackLocked(callback); - } + final boolean serviceExists = resolveService(sessionId, s -> + s.unregisterPredictionUpdates(sessionId, callback)); + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (serviceExists && sessionInfo != null) { + sessionInfo.removeCallbackLocked(callback); } } @@ -190,10 +187,7 @@ public class AppPredictionPerUserService extends */ @GuardedBy("mLock") public void requestPredictionUpdateLocked(@NonNull AppPredictionSessionId sessionId) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.requestPredictionUpdate(sessionId); - } + resolveService(sessionId, s -> s.requestPredictionUpdate(sessionId)); } /** @@ -201,14 +195,11 @@ public class AppPredictionPerUserService extends */ @GuardedBy("mLock") public void onDestroyPredictionSessionLocked(@NonNull AppPredictionSessionId sessionId) { - final RemoteAppPredictionService service = getRemoteServiceLocked(); - if (service != null) { - service.onDestroyPredictionSession(sessionId); - - AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); - if (sessionInfo != null) { - sessionInfo.destroy(); - } + final boolean serviceExists = resolveService(sessionId, s -> + s.onDestroyPredictionSession(sessionId)); + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (serviceExists && sessionInfo != null) { + sessionInfo.destroy(); } } @@ -312,6 +303,33 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") @Nullable + protected boolean resolveService(@NonNull final AppPredictionSessionId sessionId, + @NonNull final AbstractRemoteService.AsyncRequest<IPredictionService> cb) { + final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo == null) return false; + if (sessionInfo.mUsesPeopleService) { + final IPredictionService service = + LocalServices.getService(PeopleServiceInternal.class); + if (service != null) { + try { + cb.run(service); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.w(TAG, "Failed to invoke service:" + service, e); + } + } + return service != null; + } else { + final RemoteAppPredictionService service = getRemoteServiceLocked(); + if (service != null) { + service.scheduleOnResolvedService(cb); + } + return service != null; + } + } + + @GuardedBy("mLock") + @Nullable private RemoteAppPredictionService getRemoteServiceLocked() { if (mRemoteService == null) { final String serviceName = getComponentNameLocked(); @@ -334,8 +352,12 @@ public class AppPredictionPerUserService extends private static final class AppPredictionSessionInfo { private static final boolean DEBUG = false; // Do not submit with true + @NonNull private final AppPredictionSessionId mSessionId; + @NonNull private final AppPredictionContext mPredictionContext; + private final boolean mUsesPeopleService; + @NonNull private final Consumer<AppPredictionSessionId> mRemoveSessionInfoAction; private final RemoteCallbackList<IPredictionCallback> mCallbacks = @@ -352,13 +374,17 @@ public class AppPredictionPerUserService extends } }; - AppPredictionSessionInfo(AppPredictionSessionId id, AppPredictionContext predictionContext, - Consumer<AppPredictionSessionId> removeSessionInfoAction) { + AppPredictionSessionInfo( + @NonNull final AppPredictionSessionId id, + @NonNull final AppPredictionContext predictionContext, + final boolean usesPeopleService, + @NonNull final Consumer<AppPredictionSessionId> removeSessionInfoAction) { if (DEBUG) { Slog.d(TAG, "Creating AppPredictionSessionInfo for session Id=" + id); } mSessionId = id; mPredictionContext = predictionContext; + mUsesPeopleService = usesPeopleService; mRemoveSessionInfoAction = removeSessionInfoAction; } diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java index 04e0e7f7102f..ceb1cafcebeb 100644 --- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java +++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java @@ -16,13 +16,8 @@ package com.android.server.appprediction; import android.annotation.NonNull; -import android.app.prediction.AppPredictionContext; -import android.app.prediction.AppPredictionSessionId; -import android.app.prediction.AppTargetEvent; -import android.app.prediction.IPredictionCallback; import android.content.ComponentName; import android.content.Context; -import android.content.pm.ParceledListSlice; import android.os.IBinder; import android.service.appprediction.IPredictionService; import android.text.format.DateUtils; @@ -71,74 +66,17 @@ public class RemoteAppPredictionService extends } /** - * Notifies the service of a new prediction session. - */ - public void onCreatePredictionSession(@NonNull AppPredictionContext context, - @NonNull AppPredictionSessionId sessionId) { - scheduleAsyncRequest((s) -> s.onCreatePredictionSession(context, sessionId)); - } - - /** - * Records an app target event to the service. - */ - public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, - @NonNull AppTargetEvent event) { - scheduleAsyncRequest((s) -> s.notifyAppTargetEvent(sessionId, event)); - } - - /** - * Records when a launch location is shown. - */ - public void notifyLaunchLocationShown(@NonNull AppPredictionSessionId sessionId, - @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { - scheduleAsyncRequest((s) - -> s.notifyLaunchLocationShown(sessionId, launchLocation, targetIds)); - } - - /** - * Requests the service to sort a list of apps or shortcuts. - */ - public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, - @NonNull ParceledListSlice targets, @NonNull IPredictionCallback callback) { - scheduleAsyncRequest((s) -> s.sortAppTargets(sessionId, targets, callback)); - } - - - /** - * Registers a callback for continuous updates of predicted apps or shortcuts. - */ - public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, - @NonNull IPredictionCallback callback) { - scheduleAsyncRequest((s) -> s.registerPredictionUpdates(sessionId, callback)); - } - - /** - * Unregisters a callback for continuous updates of predicted apps or shortcuts. - */ - public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, - @NonNull IPredictionCallback callback) { - scheduleAsyncRequest((s) -> s.unregisterPredictionUpdates(sessionId, callback)); - } - - /** - * Requests a new set of predicted apps or shortcuts. - */ - public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { - scheduleAsyncRequest((s) -> s.requestPredictionUpdate(sessionId)); - } - - /** - * Notifies the service of the end of an existing prediction session. + * Schedules a request to bind to the remote service. */ - public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { - scheduleAsyncRequest((s) -> s.onDestroyPredictionSession(sessionId)); + public void reconnect() { + super.scheduleBind(); } /** - * Schedules a request to bind to the remote service. + * Schedule async request on remote service. */ - public void reconnect() { - super.scheduleBind(); + public void scheduleOnResolvedService(@NonNull AsyncRequest<IPredictionService> request) { + scheduleAsyncRequest(request); } /** diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 442c9e543b01..f688759e9f63 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -216,17 +216,11 @@ public abstract class UsageStatsManagerInternal { /** * Returns the events for the user in the given time period. * - * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the - * result. - * @param hideShortcutInvocationEvents whether the {@link UsageEvents.Event#SHORTCUT_INVOCATION} - * events need to be excluded from the result. - * @param hideLocusIdEvents whether the {@link UsageEvents.Event#LOCUS_ID_SET} - * events need to be excluded from the result. - * + * @param flags defines the visibility of certain usage events - see flags defined in + * {@link UsageEvents}. */ public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime, - long endTime, boolean obfuscateInstantApps, boolean hideShortcutInvocationEvents, - boolean hideLocusIdEvents); + long endTime, int flags); /** * Used to persist the last time a job was run for this app, in order to make decisions later diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index abe0dd543deb..ffa7d9202371 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -59,6 +59,8 @@ import android.app.ApplicationExitInfo.Reason; import android.app.ApplicationExitInfo.SubReason; import android.app.IApplicationThread; import android.app.IUidObserver; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -119,6 +121,7 @@ import com.android.server.pm.dex.DexManager; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowManagerService; +import dalvik.annotation.compat.VersionCodes; import dalvik.system.VMRuntime; import java.io.File; @@ -327,6 +330,15 @@ public final class ProcessList { */ private static final int PROC_KILL_TIMEOUT = 2000; // 2 seconds; + /** + * Native heap allocations will now have a non-zero tag in the most significant byte. + * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged + * Pointers</a> + */ + @ChangeId + @EnabledAfter(targetSdkVersion = VersionCodes.Q) + private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id. + ActivityManagerService mService = null; // To kill process groups asynchronously @@ -1768,6 +1780,13 @@ public final class ProcessList { runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE; } + // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level, + // or the compat feature. + if (app.info.allowsNativeHeapPointerTagging() + && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) { + runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + } + String invokeWith = null; if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { // Debuggable apps may include a wrapper script with their library directory. diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 3ffe1be89060..0edc16022640 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -56,6 +56,7 @@ import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; +import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; @@ -3365,7 +3366,8 @@ public class AppOpsService extends IAppOpsService.Stub { return false; } - return permInfo.getProtection() == PROTECTION_DANGEROUS; + return permInfo.getProtection() == PROTECTION_DANGEROUS + || (permInfo.getProtectionFlags() & PROTECTION_FLAG_APPOP) != 0; } private void verifyIncomingUid(int uid) { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index c9c2c96a642a..204f072a72fc 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -153,7 +153,12 @@ public class AuthService extends SystemService { // Only allow internal clients to enable non-public options. if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS) - || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) { + || bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false) + || bundle.getCharSequence(BiometricPrompt.KEY_DEVICE_CREDENTIAL_TITLE) != null + || bundle.getCharSequence( + BiometricPrompt.KEY_DEVICE_CREDENTIAL_SUBTITLE) != null + || bundle.getCharSequence( + BiometricPrompt.KEY_DEVICE_CREDENTIAL_DESCRIPTION) != null) { checkInternalPermission(); } diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS new file mode 100644 index 000000000000..25ef9facb216 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/OWNERS @@ -0,0 +1,6 @@ +set noparent + +ogunwale@google.com +yukawa@google.com +tarandeep@google.com +lumark@google.com diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 18383c431479..ae11c70da46a 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -257,12 +257,12 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { builder.addSelectedRoute(mSelectedRouteId); if (!TextUtils.isEmpty(activeBtDeviceAddress)) { - builder.addTransferrableRoute(mDefaultRoute.getId()); + builder.addTransferableRoute(mDefaultRoute.getId()); } for (MediaRoute2Info route : mBluetoothRoutes) { if (!TextUtils.equals(mSelectedRouteId, route.getId())) { - builder.addTransferrableRoute(route.getId()); + builder.addTransferableRoute(route.getId()); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a570502dee10..d0d0f5a3a233 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1055,7 +1055,7 @@ public class NotificationManagerService extends SystemService { if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key); reportSeen(r); } - r.setVisibility(true, nv.rank, nv.count); + r.setVisibility(true, nv.rank, nv.count, mNotificationRecordLogger); mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true); boolean isHun = (nv.location == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); @@ -1074,7 +1074,7 @@ public class NotificationManagerService extends SystemService { for (NotificationVisibility nv : noLongerVisibleKeys) { NotificationRecord r = mNotificationsByKey.get(nv.key); if (r == null) continue; - r.setVisibility(false, nv.rank, nv.count); + r.setVisibility(false, nv.rank, nv.count, mNotificationRecordLogger); mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false); nv.recycle(); } @@ -3979,6 +3979,29 @@ public class NotificationManagerService extends SystemService { } /** + * Allows the notification assistant to un-snooze a single notification. + * + * @param token The binder for the listener, to check that the caller is allowed + */ + @Override + public void unsnoozeNotificationFromSystemListener(INotificationListener token, + String key) { + long identity = Binder.clearCallingIdentity(); + try { + synchronized (mNotificationLock) { + final ManagedServiceInfo info = + mListeners.checkServiceTokenLocked(token); + if (!info.isSystem) { + throw new SecurityException("Not allowed to unsnooze before deadline"); + } + unsnoozeNotificationInt(key, info); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Allow an INotificationListener to simulate clearing (dismissing) a single notification. * * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear} @@ -6468,7 +6491,7 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerPostedByApp(r); r.setInterruptive(isVisuallyInterruptive(null, r)); } else { - old = mNotificationList.get(index); + old = mNotificationList.get(index); // Potentially *changes* old mNotificationList.set(index, r); mUsageStats.registerUpdatedByApp(r, old); // Make sure we don't lose the foreground service state. @@ -6537,7 +6560,7 @@ public class NotificationManagerService extends SystemService { maybeRecordInterruptionLocked(r); // Log event to statsd - mNotificationRecordLogger.logNotificationReported(r, old, position, + mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position, buzzBeepBlinkLoggingCode); } finally { int N = mEnqueuedNotifications.size(); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 4785da9a5922..0ada58e1ce16 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -909,7 +909,8 @@ public final class NotificationRecord { /** * Set the visibility of the notification. */ - public void setVisibility(boolean visible, int rank, int count) { + public void setVisibility(boolean visible, int rank, int count, + NotificationRecordLogger notificationRecordLogger) { final long now = System.currentTimeMillis(); mVisibleSinceMs = visible ? now : mVisibleSinceMs; stats.onVisibilityChanged(visible); @@ -927,6 +928,7 @@ public final class NotificationRecord { getFreshnessMs(now), 0, // exposure time rank); + notificationRecordLogger.logNotificationVisibility(this, visible); } /** diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index 2f7854226c5c..eaca066f026f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -41,13 +41,14 @@ import java.util.Objects; public interface NotificationRecordLogger { /** - * Logs a NotificationReported atom reflecting the posting or update of a notification. + * May log a NotificationReported atom reflecting the posting or update of a notification. * @param r The new NotificationRecord. If null, no action is taken. * @param old The previous NotificationRecord. Null if there was no previous record. * @param position The position at which this notification is ranked. * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user. */ - void logNotificationReported(@Nullable NotificationRecord r, @Nullable NotificationRecord old, + void maybeLogNotificationPosted(@Nullable NotificationRecord r, + @Nullable NotificationRecord old, int position, int buzzBeepBlink); /** @@ -62,6 +63,14 @@ public interface NotificationRecordLogger { @NotificationStats.DismissalSurface int dismissalSurface); /** + * Logs a notification visibility change event using UiEventReported (event ids from the + * NotificationEvents enum). + * @param r The NotificationRecord. If null, no action is taken. + * @param visible True if the notification became visible. + */ + void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible); + + /** * The UiEvent enums that this class can log. */ enum NotificationReportedEvent implements UiEventLogger.UiEventEnum { @@ -145,6 +154,7 @@ public interface NotificationRecordLogger { @Override public int getId() { return mId; } + public static NotificationCancelledEvent fromCancelReason( @NotificationListenerService.NotificationCancelReason int reason, @NotificationStats.DismissalSurface int surface) { @@ -191,6 +201,24 @@ public interface NotificationRecordLogger { } } + enum NotificationEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Notification became visible.") + NOTIFICATION_OPEN(197), + @UiEvent(doc = "Notification stopped being visible.") + NOTIFICATION_CLOSE(198); + + private final int mId; + NotificationEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + + public static NotificationEvent fromVisibility(boolean visible) { + return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE; + } + } /** * A helper for extracting logging information from one or two NotificationRecords. */ @@ -209,7 +237,7 @@ public interface NotificationRecordLogger { /** * @return True if old is null, alerted, or important logged fields have changed. */ - boolean shouldLog(int buzzBeepBlink) { + boolean shouldLogReported(int buzzBeepBlink) { if (r == null) { return false; } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java index bb23d1e876dc..9fcac257d328 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java @@ -26,13 +26,13 @@ import com.android.internal.util.FrameworkStatsLog; */ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { - UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); @Override - public void logNotificationReported(NotificationRecord r, NotificationRecord old, + public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { NotificationRecordPair p = new NotificationRecordPair(r, old); - if (!p.shouldLog(buzzBeepBlink)) { + if (!p.shouldLogReported(buzzBeepBlink)) { return; } FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED, @@ -66,8 +66,19 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { @Override public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { - mUiEventLogger.logWithInstanceId( - NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), - r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId()); + log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r); + } + + @Override + public void logNotificationVisibility(NotificationRecord r, boolean visible) { + log(NotificationEvent.fromVisibility(visible), r); + } + + void log(UiEventLogger.UiEventEnum event, NotificationRecord r) { + if (r == null) { + return; + } + mUiEventLogger.logWithInstanceId(event, r.getUid(), r.getSbn().getPackageName(), + r.getSbn().getInstanceId()); } } diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 661297a7346e..bae1dd3c6cb6 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -16,6 +16,7 @@ package com.android.server.notification; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -42,7 +43,9 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.PrintWriter; +import java.sql.Array; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -77,26 +80,26 @@ public class SnoozeHelper { private static final String REPOST_ACTION = SnoozeHelper.class.getSimpleName() + ".EVALUATE"; private static final int REQUEST_CODE_REPOST = 1; private static final String REPOST_SCHEME = "repost"; - private static final String EXTRA_KEY = "key"; + static final String EXTRA_KEY = "key"; private static final String EXTRA_USER_ID = "userId"; private final Context mContext; private AlarmManager mAm; private final ManagedServices.UserProfiles mUserProfiles; - // User id : package name : notification key : record. - private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>> + // User id | package name : notification key : record. + private ArrayMap<String, ArrayMap<String, NotificationRecord>> mSnoozedNotifications = new ArrayMap<>(); - // User id : package name : notification key : time-milliseconds . + // User id | package name : notification key : time-milliseconds . // This member stores persisted snoozed notification trigger times. it persists through reboots // It should have the notifications that haven't expired or re-posted yet - private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, Long>>> + private final ArrayMap<String, ArrayMap<String, Long>> mPersistedSnoozedNotifications = new ArrayMap<>(); - // User id : package name : notification key : creation ID . + // User id | package name : notification key : creation ID . // This member stores persisted snoozed notification trigger context for the assistant // it persists through reboots. // It should have the notifications that haven't expired or re-posted yet - private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, String>>> + private final ArrayMap<String, ArrayMap<String, String>> mPersistedSnoozedNotificationsWithContext = new ArrayMap<>(); // notification key : package. private ArrayMap<String, String> mPackages = new ArrayMap<>(); @@ -115,6 +118,10 @@ public class SnoozeHelper { mUserProfiles = userProfiles; } + private String getPkgKey(@UserIdInt int userId, String pkg) { + return userId + "|" + pkg; + } + void cleanupPersistedContext(String key){ int userId = mUsers.get(key); String pkg = mPackages.get(key); @@ -144,15 +151,13 @@ public class SnoozeHelper { } protected boolean isSnoozed(int userId, String pkg, String key) { - return mSnoozedNotifications.containsKey(userId) - && mSnoozedNotifications.get(userId).containsKey(pkg) - && mSnoozedNotifications.get(userId).get(pkg).containsKey(key); + return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg)) + && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key); } protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) { - if (mSnoozedNotifications.containsKey(userId) - && mSnoozedNotifications.get(userId).containsKey(pkg)) { - return mSnoozedNotifications.get(userId).get(pkg).values(); + if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) { + return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values(); } return Collections.EMPTY_LIST; } @@ -161,14 +166,14 @@ public class SnoozeHelper { ArrayList<NotificationRecord> getNotifications(String pkg, String groupKey, Integer userId) { ArrayList<NotificationRecord> records = new ArrayList<>(); - if (mSnoozedNotifications.containsKey(userId) - && mSnoozedNotifications.get(userId).containsKey(pkg)) { - ArrayMap<String, NotificationRecord> packages = - mSnoozedNotifications.get(userId).get(pkg); - for (int i = 0; i < packages.size(); i++) { - String currentGroupKey = packages.valueAt(i).getSbn().getGroup(); + ArrayMap<String, NotificationRecord> allRecords = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (allRecords != null) { + for (int i = 0; i < allRecords.size(); i++) { + NotificationRecord r = allRecords.valueAt(i); + String currentGroupKey = r.getSbn().getGroup(); if (currentGroupKey.equals(groupKey)) { - records.add(packages.valueAt(i)); + records.add(r); } } } @@ -176,47 +181,30 @@ public class SnoozeHelper { } protected NotificationRecord getNotification(String key) { - List<NotificationRecord> snoozedForUser = new ArrayList<>(); - IntArray userIds = mUserProfiles.getCurrentProfileIds(); - if (userIds != null) { - final int userIdsSize = userIds.size(); - for (int i = 0; i < userIdsSize; i++) { - final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userIds.get(i)); - if (snoozedPkgs != null) { - final int snoozedPkgsSize = snoozedPkgs.size(); - for (int j = 0; j < snoozedPkgsSize; j++) { - final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j); - if (records != null) { - return records.get(key); - } - } - } - } + if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) { + Slog.w(TAG, "Snoozed data sets no longer agree for " + key); + return null; + } + int userId = mUsers.get(key); + String pkg = mPackages.get(key); + ArrayMap<String, NotificationRecord> snoozed = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (snoozed == null) { + return null; } - return null; + return snoozed.get(key); } protected @NonNull List<NotificationRecord> getSnoozed() { - List<NotificationRecord> snoozedForUser = new ArrayList<>(); - IntArray userIds = mUserProfiles.getCurrentProfileIds(); - if (userIds != null) { - final int N = userIds.size(); - for (int i = 0; i < N; i++) { - final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userIds.get(i)); - if (snoozedPkgs != null) { - final int M = snoozedPkgs.size(); - for (int j = 0; j < M; j++) { - final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j); - if (records != null) { - snoozedForUser.addAll(records.values()); - } - } - } - } - } - return snoozedForUser; + // caller filters records based on the current user profiles and listener access, so just + // return everything + List<NotificationRecord> snoozed= new ArrayList<>(); + for (String userPkgKey : mSnoozedNotifications.keySet()) { + ArrayMap<String, NotificationRecord> snoozedRecords = + mSnoozedNotifications.get(userPkgKey); + snoozed.addAll(snoozedRecords.values()); + } + return snoozed; } /** @@ -261,120 +249,88 @@ public class SnoozeHelper { } private <T> void storeRecord(String pkg, String key, Integer userId, - ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, T object) { + ArrayMap<String, ArrayMap<String, T>> targets, T object) { - ArrayMap<String, ArrayMap<String, T>> records = - targets.get(userId); - if (records == null) { - records = new ArrayMap<>(); - } - ArrayMap<String, T> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - pkgRecords = new ArrayMap<>(); + ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); + if (keyToValue == null) { + keyToValue = new ArrayMap<>(); } - pkgRecords.put(key, object); - records.put(pkg, pkgRecords); - targets.put(userId, records); + keyToValue.put(key, object); + targets.put(getPkgKey(userId, pkg), keyToValue); } private <T> T removeRecord(String pkg, String key, Integer userId, - ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets) { + ArrayMap<String, ArrayMap<String, T>> targets) { T object = null; - - ArrayMap<String, ArrayMap<String, T>> records = - targets.get(userId); - if (records == null) { + ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg)); + if (keyToValue == null) { return null; } - ArrayMap<String, T> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - return null; - } - object = pkgRecords.remove(key); - if (pkgRecords.size() == 0) { - records.remove(pkg); - } - if (records.size() == 0) { - targets.remove(userId); + object = keyToValue.remove(key); + if (keyToValue.size() == 0) { + targets.remove(getPkgKey(userId, pkg)); } return object; } protected boolean cancel(int userId, String pkg, String tag, int id) { - if (mSnoozedNotifications.containsKey(userId)) { - ArrayMap<String, NotificationRecord> recordsForPkg = - mSnoozedNotifications.get(userId).get(pkg); - if (recordsForPkg != null) { - final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet(); - for (Map.Entry<String, NotificationRecord> record : records) { - final StatusBarNotification sbn = record.getValue().getSbn(); - if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) { - record.getValue().isCanceled = true; - return true; - } + ArrayMap<String, NotificationRecord> recordsForPkg = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (recordsForPkg != null) { + final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet(); + for (Map.Entry<String, NotificationRecord> record : records) { + final StatusBarNotification sbn = record.getValue().getSbn(); + if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) { + record.getValue().isCanceled = true; + return true; } } } return false; } - protected boolean cancel(int userId, boolean includeCurrentProfiles) { - int[] userIds = {userId}; + protected void cancel(int userId, boolean includeCurrentProfiles) { + if (mSnoozedNotifications.size() == 0) { + return; + } + IntArray userIds = new IntArray(); + userIds.add(userId); if (includeCurrentProfiles) { - userIds = mUserProfiles.getCurrentProfileIds().toArray(); + userIds = mUserProfiles.getCurrentProfileIds(); } - final int N = userIds.length; - for (int i = 0; i < N; i++) { - final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userIds[i]); - if (snoozedPkgs != null) { - final int M = snoozedPkgs.size(); - for (int j = 0; j < M; j++) { - final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j); - if (records != null) { - int P = records.size(); - for (int k = 0; k < P; k++) { - records.valueAt(k).isCanceled = true; - } - } + for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) { + for (NotificationRecord r : snoozedRecords.values()) { + if (userIds.binarySearch(r.getUserId()) >= 0) { + r.isCanceled = true; } - return true; } } - return false; } protected boolean cancel(int userId, String pkg) { - if (mSnoozedNotifications.containsKey(userId)) { - if (mSnoozedNotifications.get(userId).containsKey(pkg)) { - ArrayMap<String, NotificationRecord> records = - mSnoozedNotifications.get(userId).get(pkg); - int N = records.size(); - for (int i = 0; i < N; i++) { - records.valueAt(i).isCanceled = true; - } - return true; - } + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (records == null) { + return false; } - return false; + int N = records.size(); + for (int i = 0; i < N; i++) { + records.valueAt(i).isCanceled = true; + } + return true; } /** * Updates the notification record so the most up to date information is shown on re-post. */ protected void update(int userId, NotificationRecord record) { - ArrayMap<String, ArrayMap<String, NotificationRecord>> records = - mSnoozedNotifications.get(userId); + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName())); if (records == null) { return; } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(record.getSbn().getPackageName()); - if (pkgRecords == null) { - return; - } - NotificationRecord existing = pkgRecords.get(record.getKey()); - pkgRecords.put(record.getKey(), record); + records.put(record.getKey(), record); } protected void repost(String key) { @@ -386,20 +342,18 @@ public class SnoozeHelper { protected void repost(String key, int userId) { final String pkg = mPackages.remove(key); - ArrayMap<String, ArrayMap<String, NotificationRecord>> records = - mSnoozedNotifications.get(userId); + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { return; } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - return; - } - final NotificationRecord record = pkgRecords.remove(key); + final NotificationRecord record = records.remove(key); mPackages.remove(key); mUsers.remove(key); if (record != null && !record.isCanceled) { + final PendingIntent pi = createPendingIntent(pkg, record.getKey(), userId); + mAm.cancel(pi); MetricsLogger.action(record.getLogMaker() .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); @@ -408,55 +362,46 @@ public class SnoozeHelper { } protected void repostGroupSummary(String pkg, int userId, String groupKey) { - if (mSnoozedNotifications.containsKey(userId)) { - ArrayMap<String, ArrayMap<String, NotificationRecord>> keysByPackage - = mSnoozedNotifications.get(userId); - - if (keysByPackage != null && keysByPackage.containsKey(pkg)) { - ArrayMap<String, NotificationRecord> recordsByKey = keysByPackage.get(pkg); - - if (recordsByKey != null) { - String groupSummaryKey = null; - int N = recordsByKey.size(); - for (int i = 0; i < N; i++) { - final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i); - if (potentialGroupSummary.getSbn().isGroup() - && potentialGroupSummary.getNotification().isGroupSummary() - && groupKey.equals(potentialGroupSummary.getGroupKey())) { - groupSummaryKey = potentialGroupSummary.getKey(); - break; - } - } + ArrayMap<String, NotificationRecord> recordsByKey + = mSnoozedNotifications.get(getPkgKey(userId, pkg)); + if (recordsByKey == null) { + return; + } - if (groupSummaryKey != null) { - NotificationRecord record = recordsByKey.remove(groupSummaryKey); - mPackages.remove(groupSummaryKey); - mUsers.remove(groupSummaryKey); + String groupSummaryKey = null; + int N = recordsByKey.size(); + for (int i = 0; i < N; i++) { + final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i); + if (potentialGroupSummary.getSbn().isGroup() + && potentialGroupSummary.getNotification().isGroupSummary() + && groupKey.equals(potentialGroupSummary.getGroupKey())) { + groupSummaryKey = potentialGroupSummary.getKey(); + break; + } + } - if (record != null && !record.isCanceled) { - MetricsLogger.action(record.getLogMaker() - .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) - .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); - mCallback.repost(userId, record); - } - } - } + if (groupSummaryKey != null) { + NotificationRecord record = recordsByKey.remove(groupSummaryKey); + mPackages.remove(groupSummaryKey); + mUsers.remove(groupSummaryKey); + + if (record != null && !record.isCanceled) { + MetricsLogger.action(record.getLogMaker() + .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED) + .setType(MetricsProto.MetricsEvent.TYPE_OPEN)); + mCallback.repost(userId, record); } } } protected void clearData(int userId, String pkg) { - ArrayMap<String, ArrayMap<String, NotificationRecord>> records = - mSnoozedNotifications.get(userId); + ArrayMap<String, NotificationRecord> records = + mSnoozedNotifications.get(getPkgKey(userId, pkg)); if (records == null) { return; } - ArrayMap<String, NotificationRecord> pkgRecords = records.get(pkg); - if (pkgRecords == null) { - return; - } - for (int i = pkgRecords.size() - 1; i >= 0; i--) { - final NotificationRecord r = pkgRecords.removeAt(i); + for (int i = records.size() - 1; i >= 0; i--) { + final NotificationRecord r = records.removeAt(i); if (r != null) { mPackages.remove(r.getKey()); mUsers.remove(r.getKey()); @@ -495,22 +440,36 @@ public class SnoozeHelper { public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) { pw.println("\n Snoozed notifications:"); - for (int userId : mSnoozedNotifications.keySet()) { + for (String userPkgKey : mSnoozedNotifications.keySet()) { pw.print(INDENT); - pw.println("user: " + userId); - ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs = - mSnoozedNotifications.get(userId); - for (String pkg : snoozedPkgs.keySet()) { + pw.println("key: " + userPkgKey); + ArrayMap<String, NotificationRecord> snoozedRecords = + mSnoozedNotifications.get(userPkgKey); + Set<String> snoozedKeys = snoozedRecords.keySet(); + for (String key : snoozedKeys) { pw.print(INDENT); pw.print(INDENT); - pw.println("package: " + pkg); - Set<String> snoozedKeys = snoozedPkgs.get(pkg).keySet(); - for (String key : snoozedKeys) { - pw.print(INDENT); - pw.print(INDENT); - pw.print(INDENT); - pw.println(key); - } + pw.print(INDENT); + pw.println(key); + } + } + pw.println("\n Pending snoozed notifications"); + for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) { + pw.print(INDENT); + pw.println("key: " + userPkgKey); + ArrayMap<String, Long> snoozedRecords = + mPersistedSnoozedNotifications.get(userPkgKey); + if (snoozedRecords == null) { + continue; + } + Set<String> snoozedKeys = snoozedRecords.keySet(); + for (String key : snoozedKeys) { + pw.print(INDENT); + pw.print(INDENT); + pw.print(INDENT); + pw.print(key); + pw.print(INDENT); + pw.println(snoozedRecords.get(key)); } } } @@ -538,41 +497,34 @@ public class SnoozeHelper { void insert(T t) throws IOException; } private <T> void writeXml(XmlSerializer out, - ArrayMap<Integer, ArrayMap<String, ArrayMap<String, T>>> targets, String tag, + ArrayMap<String, ArrayMap<String, T>> targets, String tag, Inserter<T> attributeInserter) throws IOException { synchronized (targets) { final int M = targets.size(); for (int i = 0; i < M; i++) { - final ArrayMap<String, ArrayMap<String, T>> packages = - targets.valueAt(i); - if (packages == null) { - continue; - } - final int N = packages.size(); - for (int j = 0; j < N; j++) { - final ArrayMap<String, T> keyToValue = packages.valueAt(j); - if (keyToValue == null) { - continue; - } - final int O = keyToValue.size(); - for (int k = 0; k < O; k++) { - T value = keyToValue.valueAt(k); + String userIdPkgKey = targets.keyAt(i); + // T is a String (snoozed until context) or Long (snoozed until time) + ArrayMap<String, T> keyToValue = targets.valueAt(i); + for (int j = 0; j < keyToValue.size(); j++) { + String key = keyToValue.keyAt(j); + T value = keyToValue.valueAt(j); - out.startTag(null, tag); + out.startTag(null, tag); - attributeInserter.insert(value); + attributeInserter.insert(value); - out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, - XML_SNOOZED_NOTIFICATION_VERSION); - out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, keyToValue.keyAt(k)); - out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, packages.keyAt(j)); - out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID, - targets.keyAt(i).toString()); + out.attribute(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, + XML_SNOOZED_NOTIFICATION_VERSION); + out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key); - out.endTag(null, tag); + String pkg = mPackages.get(key); + int userId = mUsers.get(key); + out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg); + out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID, + String.valueOf(userId)); - } + out.endTag(null, tag); } } } @@ -606,7 +558,6 @@ public class SnoozeHelper { } scheduleRepost(pkg, key, userId, time - System.currentTimeMillis()); } - continue; } if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) { final String creationId = parser.getAttributeValue( @@ -615,18 +566,9 @@ public class SnoozeHelper { storeRecord(pkg, key, userId, mPersistedSnoozedNotificationsWithContext, creationId); } - continue; } - - } catch (Exception e) { - //we dont cre if it is a number format exception or a null pointer exception. - //we just want to debug it and continue with our lives - if (DEBUG) { - Slog.d(TAG, - "Exception in reading snooze data from policy xml: " - + e.getMessage()); - } + Slog.e(TAG, "Exception in reading snooze data from policy xml", e); } } } diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java index c5b868fbe99d..31fa4d192278 100644 --- a/services/core/java/com/android/server/people/PeopleServiceInternal.java +++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java @@ -17,6 +17,8 @@ package com.android.server.people; import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.os.CancellationSignal; import android.service.appprediction.IPredictionService; /** @@ -25,16 +27,23 @@ import android.service.appprediction.IPredictionService; public abstract class PeopleServiceInternal extends IPredictionService.Stub { /** + * Prunes the data for the specified user. Called by {@link + * com.android.server.people.data.DataMaintenanceService} when the device is idle. + */ + public abstract void pruneDataForUser(@UserIdInt int userId, + @NonNull CancellationSignal signal); + + /** * The number conversation infos will be dynamic, based on the currently installed apps on the * device. All of which should be combined into a single blob to be backed up. */ - public abstract byte[] backupConversationInfos(@NonNull int userId); + public abstract byte[] backupConversationInfos(@UserIdInt int userId); /** * Multiple conversation infos may exist in the restore payload, child classes are required to * manage the restoration based on how individual conversation infos were originally combined * during backup. */ - public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key, + public abstract void restoreConversationInfos(@UserIdInt int userId, @NonNull String key, @NonNull byte[] payload); } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index d629b547992b..0fb889c8da22 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -69,7 +69,7 @@ public class AppsFilter { // Logs all filtering instead of enforcing private static final boolean DEBUG_ALLOW_ALL = false; private static final boolean DEBUG_LOGGING = false; - private static final boolean FEATURE_ENABLED_BY_DEFAULT = false; + private static final boolean FEATURE_ENABLED_BY_DEFAULT = true; /** * This contains a list of app UIDs that are implicitly queryable because another app explicitly diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 064fd3f4e0b2..8c6e6916ec86 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14707,8 +14707,7 @@ public class PackageManagerService extends IPackageManager.Stub void handleVerificationFinished() { if (!mVerificationCompleted) { mVerificationCompleted = true; - if (mIntegrityVerificationCompleted || mRet != INSTALL_SUCCEEDED) { - mIntegrityVerificationCompleted = true; + if (mIntegrityVerificationCompleted) { handleReturnCode(); } // integrity verification still pending. @@ -14718,8 +14717,7 @@ public class PackageManagerService extends IPackageManager.Stub void handleIntegrityVerificationFinished() { if (!mIntegrityVerificationCompleted) { mIntegrityVerificationCompleted = true; - if (mVerificationCompleted || mRet != INSTALL_SUCCEEDED) { - mVerificationCompleted = true; + if (mVerificationCompleted) { handleReturnCode(); } // verifier still pending diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index c267cea163d5..f1e403b1bc63 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -119,6 +119,7 @@ import java.io.PrintWriter; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -135,7 +136,6 @@ import java.util.concurrent.TimeUnit; class PackageManagerShellCommand extends ShellCommand { /** Path for streaming APK content */ private static final String STDIN_PATH = "-"; - private static final byte[] STDIN_PATH_BYTES = "-".getBytes(StandardCharsets.UTF_8); /** Path where ART profiles snapshots are dumped for the shell user */ private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/"; private static final int DEFAULT_WAIT_MS = 60 * 1000; @@ -2988,8 +2988,10 @@ class PackageManagerShellCommand extends ShellCommand { try { // 1. Single file from stdin. if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) { - String name = "base." + (isApex ? "apex" : "apk"); - session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes, STDIN_PATH_BYTES, null); + final String name = "base." + (isApex ? "apex" : "apk"); + final String metadata = "-" + name; + session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes, + metadata.getBytes(StandardCharsets.UTF_8), null); return 0; } @@ -2998,24 +3000,58 @@ class PackageManagerShellCommand extends ShellCommand { // 2. File with specified size read from stdin. if (delimLocation != -1) { - String name = arg.substring(0, delimLocation); - String sizeStr = arg.substring(delimLocation + 1); - long sizeBytes; + final String[] fileDesc = arg.split(":"); + String name = null; + long sizeBytes = -1; + String metadata; + byte[] signature = null; + + try { + if (fileDesc.length > 0) { + name = fileDesc[0]; + } + if (fileDesc.length > 1) { + sizeBytes = Long.parseUnsignedLong(fileDesc[1]); + } + if (fileDesc.length > 2 && !TextUtils.isEmpty(fileDesc[2])) { + metadata = fileDesc[2]; + } else { + metadata = name; + } + if (fileDesc.length > 3) { + signature = Base64.getDecoder().decode(fileDesc[3]); + } + } catch (IllegalArgumentException e) { + getErrPrintWriter().println( + "Unable to parse file parameters: " + arg + ", reason: " + e); + return 1; + } if (TextUtils.isEmpty(name)) { getErrPrintWriter().println("Empty file name in: " + arg); return 1; } + + if (signature != null) { + // Streaming/adb mode. + metadata = "+" + metadata; + } else { + // Singleshot read from stdin. + metadata = "-" + metadata; + } + try { - sizeBytes = Long.parseUnsignedLong(sizeStr); - } catch (NumberFormatException e) { - getErrPrintWriter().println("Unable to parse size from: " + arg); + if (V4Signature.readFrom(signature) == null) { + getErrPrintWriter().println("V4 signature is invalid in: " + arg); + return 1; + } + } catch (Exception e) { + getErrPrintWriter().println("V4 signature is invalid: " + e + " in " + arg); return 1; } - // Incremental requires unique metadatas, let's add a name to the dash. session.addFile(LOCATION_DATA_APP, name, sizeBytes, - ("-" + name).getBytes(StandardCharsets.UTF_8), null); + metadata.getBytes(StandardCharsets.UTF_8), signature); continue; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 0fb4cb036282..832c9b788b0b 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4393,6 +4393,7 @@ public final class Settings { ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT, "SYSTEM_EXT", ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD, "VIRTUAL_PRELOAD", ApplicationInfo.PRIVATE_FLAG_ODM, "ODM", + ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING, "PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING", }; void dumpVersionLPr(IndentingPrintWriter pw) { diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 67a22d3e477c..39093aec07c0 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -866,6 +866,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { } } + default int getMaxWindowLayer() { + return 35; + } + /** * Return how to Z-order sub-windows in relation to the window they are attached to. * Return positive to have them ordered in front, negative for behind. diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 9e150fd6a8b3..f9981d0d9c96 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -802,7 +802,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { newRollback = getRollbackForSessionLocked(packageSession.getSessionId()); if (newRollback == null) { newRollback = createNewRollbackLocked(parentSession); - mRollbacks.add(newRollback); } } newRollback.addToken(token); @@ -1002,11 +1001,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } } - Rollback rollback = completeEnableRollback(newRollback); - if (rollback == null) { + if (!completeEnableRollback(newRollback)) { result.offer(-1); } else { - result.offer(rollback.info.getRollbackId()); + result.offer(newRollback.info.getRollbackId()); } }); @@ -1158,19 +1156,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Rollback rollback; synchronized (mLock) { rollback = getRollbackForSessionLocked(sessionId); - if (rollback == null || rollback.isStaged() || !rollback.isEnabling() - || !rollback.notifySessionWithSuccess()) { - return; - } - // All child sessions finished with success. We can enable this rollback now. - // TODO: refactor #completeEnableRollback so we won't remove 'rollback' from - // mRollbacks here and add it back in #completeEnableRollback later. - mRollbacks.remove(rollback); } - // TODO: Now #completeEnableRollback returns the same rollback object as the - // parameter on success. It would be more readable to return a boolean to indicate - // success or failure. - if (completeEnableRollback(rollback) != null) { + if (rollback != null && !rollback.isStaged() && rollback.isEnabling() + && rollback.notifySessionWithSuccess() + && completeEnableRollback(rollback)) { makeRollbackAvailable(rollback); } } else { @@ -1188,13 +1177,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } /** - * Add a rollback to the list of rollbacks. It does not make the rollback available yet. + * Persist a rollback as enable-completed. It does not make the rollback available yet. + * This rollback will be deleted and removed from {@link #mRollbacks} should any error happens. * - * @return the Rollback instance for a successfully enable-completed rollback, - * or null on error. + * @return {code true} if {code rollback} is successfully enable-completed, + * or {code false} otherwise. */ @WorkerThread - private Rollback completeEnableRollback(Rollback rollback) { + private boolean completeEnableRollback(Rollback rollback) { if (LOCAL_LOGV) { Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId()); } @@ -1205,26 +1195,24 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // rollback for the embedded apk-in-apex, if any. if (!rollback.allPackagesEnabled()) { Slog.e(TAG, "Failed to enable rollback for all packages in session."); + mRollbacks.remove(rollback); rollback.delete(mAppDataRollbackHelper); - return null; + return false; } + // Note: There is a small window of time between when + // the session has been committed by the package + // manager and when we make the rollback available + // here. Presumably the window is small enough that + // nobody will want to roll back the newly installed + // package before we make the rollback available. + // TODO: We'll lose the rollback if the + // device reboots between when the session is + // committed and this point. Revisit this after + // adding support for rollback of staged installs. rollback.saveRollback(); - synchronized (mLock) { - // Note: There is a small window of time between when - // the session has been committed by the package - // manager and when we make the rollback available - // here. Presumably the window is small enough that - // nobody will want to roll back the newly installed - // package before we make the rollback available. - // TODO: We'll lose the rollback if the - // device reboots between when the session is - // committed and this point. Revisit this after - // adding support for rollback of staged installs. - mRollbacks.add(rollback); - } - return rollback; + return true; } @WorkerThread @@ -1304,6 +1292,10 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } } + /** + * Creates and returns a Rollback according to the given SessionInfo + * and adds it to {@link #mRollbacks}. + */ @WorkerThread @GuardedBy("mLock") private Rollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) { @@ -1338,6 +1330,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { installerPackageName, packageSessionIds); } + mRollbacks.add(rollback); return rollback; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 1859fae5e7fc..f2917c54c3fa 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5568,11 +5568,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (mLastResumedActivity != null && r.mUserId != mLastResumedActivity.mUserId) { mAmInternal.sendForegroundProfileChanged(r.mUserId); } + final Task prevTask = mLastResumedActivity != null ? mLastResumedActivity.getTask() : null; + updateResumedAppTrace(r); mLastResumedActivity = r; r.getDisplay().setFocusedApp(r, true); + if (prevTask == null || task != prevTask) { + if (prevTask != null) { + mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false); + } + mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true); + } + applyUpdateLockStateLocked(r); applyUpdateVrModeLocked(r); diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index b3edc91a4129..0d365b16e228 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -102,6 +102,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { } @Override + public String toString() { + return mName + "@" + System.identityHashCode(this); + } + + @Override public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) { final long token = proto.start(fieldId); super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 9e93e1455f2c..0ec0c7b53875 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -16,8 +16,6 @@ package com.android.server.wm; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; - import android.content.res.Resources; import android.text.TextUtils; @@ -43,7 +41,7 @@ public abstract class DisplayAreaPolicy { /** * The Tasks container. Tasks etc. are automatically added to this container. */ - protected final TaskContainers mTaskContainers; + protected final DisplayArea<? extends ActivityStack> mTaskContainers; /** * Construct a new {@link DisplayAreaPolicy} @@ -58,7 +56,8 @@ public abstract class DisplayAreaPolicy { */ protected DisplayAreaPolicy(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, - DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { + DisplayArea<? extends WindowContainer> imeContainer, + DisplayArea<? extends ActivityStack> taskContainers) { mWmService = wmService; mContent = content; mRoot = root; @@ -83,67 +82,15 @@ public abstract class DisplayAreaPolicy { */ public abstract void addWindow(WindowToken token); - /** - * Default policy that has no special features. - */ - public static class Default extends DisplayAreaPolicy { - - public Default(WindowManagerService wmService, DisplayContent content, - DisplayArea.Root root, + /** Provider for platform-default display area policy. */ + static final class DefaultProvider implements DisplayAreaPolicy.Provider { + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { - super(wmService, content, root, imeContainer, taskContainers); - } - - private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService, - DisplayArea.Type.BELOW_TASKS, "BelowTasks"); - private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService, - DisplayArea.Type.ABOVE_TASKS, "AboveTasks"); - private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService, - DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme"); - private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService, - DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme"); - - @Override - public void attachDisplayAreas() { - mRoot.addChild(mBelow, 0); - mRoot.addChild(mTaskContainers, 1); - mRoot.addChild(mAbove, 2); - - mAbove.addChild(mAboveBelowIme, 0); - mAbove.addChild(mImeContainer, 1); - mAbove.addChild(mAboveAboveIme, 2); - } - - @Override - public void addWindow(WindowToken token) { - switch (DisplayArea.Type.typeOf(token)) { - case ABOVE_TASKS: - if (token.getWindowLayerFromType() - < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) { - mAboveBelowIme.addChild(token); - } else { - mAboveAboveIme.addChild(token); - } - break; - case BELOW_TASKS: - mBelow.addChild(token); - break; - default: - throw new IllegalArgumentException("don't know how to sort " + token); - } - } - - /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */ - static class Provider implements DisplayAreaPolicy.Provider { - @Override - public DisplayAreaPolicy instantiate(WindowManagerService wmService, - DisplayContent content, DisplayArea.Root root, - DisplayArea<? extends WindowContainer> imeContainer, - TaskContainers taskContainers) { - return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer, - taskContainers); - } + return new DisplayAreaPolicyBuilder() + .build(wmService, content, root, imeContainer, taskContainers); } } @@ -172,7 +119,7 @@ public abstract class DisplayAreaPolicy { String name = res.getString( com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider); if (TextUtils.isEmpty(name)) { - return new DisplayAreaPolicy.Default.Provider(); + return new DisplayAreaPolicy.DefaultProvider(); } try { return (Provider) Class.forName(name).newInstance(); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java new file mode 100644 index 000000000000..885456a8488c --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -0,0 +1,371 @@ +/* + * 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.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; +import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.policy.WindowManagerPolicy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A builder for instantiating a complex {@link DisplayAreaPolicy} + * + * <p>Given a set of features (that each target a set of window types), it builds the necessary + * DisplayArea hierarchy. + * + * <p>Example: <br /> + * + * <pre> + * // Feature for targeting everything below the magnification overlay: + * new DisplayAreaPolicyBuilder(...) + * .addFeature(new Feature.Builder(..., "Magnification") + * .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY) + * .build()) + * .build(...) + * + * // Builds a policy with the following hierarchy: + * - DisplayArea.Root + * - Magnification + * - DisplayArea.Tokens (Wallpapers are attached here) + * - TaskContainers + * - DisplayArea.Tokens (windows above Tasks up to IME are attached here) + * - ImeContainers + * - DisplayArea.Tokens (windows above IME up to TYPE_ACCESSIBILITY_OVERLAY attached here) + * - DisplayArea.Tokens (TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY and up are attached here) + * + * </pre> + * + * // TODO(display-area): document more complex scenarios where we need multiple areas per feature. + */ +class DisplayAreaPolicyBuilder { + + private final ArrayList<Feature> mFeatures = new ArrayList<>(); + + /** + * A feature that requires {@link DisplayArea DisplayArea(s)}. + */ + static class Feature { + private final String mName; + private final boolean[] mWindowLayers; + + private Feature(String name, boolean[] windowLayers) { + mName = name; + mWindowLayers = windowLayers; + } + + static class Builder { + private final WindowManagerPolicy mPolicy; + private final String mName; + private final boolean[] mLayers; + + /** + * Build a new feature that applies to a set of window types as specified by the builder + * methods. + * + * <p>The set of types is updated iteratively in the order of the method invocations. + * For example, {@code all().except(TYPE_STATUS_BAR)} expresses that a feature should + * apply to all types except TYPE_STATUS_BAR. + * + * The builder starts out with the feature not applying to any types. + * + * @param name the name of the feature. + */ + Builder(WindowManagerPolicy policy, String name) { + mPolicy = policy; + mName = name; + mLayers = new boolean[mPolicy.getMaxWindowLayer()]; + } + + /** + * Set that the feature applies to all window types. + */ + Builder all() { + Arrays.fill(mLayers, true); + return this; + } + + /** + * Set that the feature applies to the given window types. + */ + Builder and(int... types) { + for (int i = 0; i < types.length; i++) { + int type = types[i]; + set(type, true); + } + return this; + } + + /** + * Set that the feature does not apply to the given window types. + */ + Builder except(int... types) { + for (int i = 0; i < types.length; i++) { + int type = types[i]; + set(type, false); + } + return this; + } + + /** + * Set that the feature applies window types that are layerd at or below the layer of + * the given window type. + */ + Builder upTo(int typeInclusive) { + final int max = layerFromType(typeInclusive, false); + for (int i = 0; i < max; i++) { + mLayers[i] = true; + } + set(typeInclusive, true); + return this; + } + + Feature build() { + return new Feature(mName, mLayers.clone()); + } + + private void set(int type, boolean value) { + mLayers[layerFromType(type, true)] = value; + if (type == TYPE_APPLICATION_OVERLAY) { + mLayers[layerFromType(type, true)] = value; + mLayers[layerFromType(TYPE_SYSTEM_ALERT, false)] = value; + mLayers[layerFromType(TYPE_SYSTEM_OVERLAY, false)] = value; + mLayers[layerFromType(TYPE_SYSTEM_ERROR, false)] = value; + } + } + + private int layerFromType(int type, boolean internalWindows) { + return mPolicy.getWindowLayerFromTypeLw(type, internalWindows); + } + } + } + + static class Result extends DisplayAreaPolicy { + private static final int LEAF_TYPE_TASK_CONTAINERS = 1; + private static final int LEAF_TYPE_IME_CONTAINERS = 2; + private static final int LEAF_TYPE_TOKENS = 0; + + private final int mMaxWindowLayer = mWmService.mPolicy.getMaxWindowLayer(); + + private final ArrayList<Feature> mFeatures; + private final Map<Feature, List<DisplayArea<? extends WindowContainer>>> mAreas; + private final DisplayArea.Tokens[] mAreaForLayer = new DisplayArea.Tokens[mMaxWindowLayer]; + + Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + DisplayArea<? extends ActivityStack> taskStacks, ArrayList<Feature> features) { + super(wmService, content, root, imeContainer, taskStacks); + mFeatures = features; + mAreas = new HashMap<>(features.size()); + for (int i = 0; i < mFeatures.size(); i++) { + mAreas.put(mFeatures.get(i), new ArrayList<>()); + } + } + + @Override + public void attachDisplayAreas() { + // This method constructs the layer hierarchy with the following properties: + // (1) Every feature maps to a set of DisplayAreas + // (2) After adding a window, for every feature the window's type belongs to, + // it is a descendant of one of the corresponding DisplayAreas of the feature. + // (3) Z-order is maintained, i.e. if z-range(area) denotes the set of layers of windows + // within a DisplayArea: + // for every pair of DisplayArea siblings (a,b), where a is below b, it holds that + // max(z-range(a)) <= min(z-range(b)) + // + // The algorithm below iteratively creates such a hierarchy: + // - Initially, all windows are attached to the root. + // - For each feature we create a set of DisplayAreas, by looping over the layers + // - if the feature does apply to the current layer, we need to find a DisplayArea + // for it to satisfy (2) + // - we can re-use the previous layer's area if: + // the current feature also applies to the previous layer, (to satisfy (3)) + // and the last feature that applied to the previous layer is the same as + // the last feature that applied to the current layer (to satisfy (2)) + // - otherwise we create a new DisplayArea below the last feature that applied + // to the current layer + + + PendingArea[] areaForLayer = new PendingArea[mMaxWindowLayer]; + final PendingArea root = new PendingArea(null, 0, null); + Arrays.fill(areaForLayer, root); + + final int size = mFeatures.size(); + for (int i = 0; i < size; i++) { + PendingArea featureArea = null; + for (int layer = 0; layer < mMaxWindowLayer; layer++) { + final Feature feature = mFeatures.get(i); + if (feature.mWindowLayers[layer]) { + if (featureArea == null || featureArea.mParent != areaForLayer[layer]) { + // No suitable DisplayArea - create a new one under the previous area + // for this layer. + featureArea = new PendingArea(feature, layer, areaForLayer[layer]); + areaForLayer[layer].mChildren.add(featureArea); + } + areaForLayer[layer] = featureArea; + } else { + featureArea = null; + } + } + } + + PendingArea leafArea = null; + int leafType = LEAF_TYPE_TOKENS; + for (int layer = 0; layer < mMaxWindowLayer; layer++) { + int type = typeOfLayer(mWmService.mPolicy, layer); + if (leafArea == null || leafArea.mParent != areaForLayer[layer] + || type != leafType) { + leafArea = new PendingArea(null, layer, areaForLayer[layer]); + areaForLayer[layer].mChildren.add(leafArea); + leafType = type; + if (leafType == LEAF_TYPE_TASK_CONTAINERS) { + leafArea.mExisting = mTaskContainers; + } else if (leafType == LEAF_TYPE_IME_CONTAINERS) { + leafArea.mExisting = mImeContainer; + } + } + leafArea.mMaxLayer = layer; + } + root.computeMaxLayer(); + root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas); + } + + @Override + public void addWindow(WindowToken token) { + DisplayArea.Tokens area = findAreaForToken(token); + area.addChild(token); + } + + @VisibleForTesting + DisplayArea.Tokens findAreaForToken(WindowToken token) { + int windowLayerFromType = token.getWindowLayerFromType(); + if (windowLayerFromType == APPLICATION_LAYER) { + // TODO(display-area): Better handle AboveAppWindows in APPLICATION_LAYER + windowLayerFromType += 1; + } else if (token.mRoundedCornerOverlay) { + windowLayerFromType = mMaxWindowLayer - 1; + } + return mAreaForLayer[windowLayerFromType]; + } + + public List<DisplayArea<? extends WindowContainer>> getDisplayAreas(Feature feature) { + return mAreas.get(feature); + } + + private static int typeOfLayer(WindowManagerPolicy policy, int layer) { + if (layer == APPLICATION_LAYER) { + return LEAF_TYPE_TASK_CONTAINERS; + } else if (layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD) + || layer == policy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD_DIALOG)) { + return LEAF_TYPE_IME_CONTAINERS; + } else { + return LEAF_TYPE_TOKENS; + } + } + } + + DisplayAreaPolicyBuilder addFeature(Feature feature) { + mFeatures.add(feature); + return this; + } + + Result build(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + DisplayArea<? extends ActivityStack> taskContainers) { + + return new Result(wmService, content, root, imeContainer, taskContainers, new ArrayList<>( + mFeatures)); + } + + static class PendingArea { + final int mMinLayer; + final ArrayList<PendingArea> mChildren = new ArrayList<>(); + final Feature mFeature; + final PendingArea mParent; + int mMaxLayer; + DisplayArea mExisting; + + PendingArea(Feature feature, + int minLayer, + PendingArea parent) { + mMinLayer = minLayer; + mFeature = feature; + mParent = parent; + } + + int computeMaxLayer() { + for (int i = 0; i < mChildren.size(); i++) { + mMaxLayer = Math.max(mMaxLayer, mChildren.get(i).computeMaxLayer()); + } + return mMaxLayer; + } + + void instantiateChildren(DisplayArea<DisplayArea> parent, + DisplayArea.Tokens[] areaForLayer, int level, Map<Feature, List<DisplayArea<? + extends WindowContainer>>> areas) { + mChildren.sort(Comparator.comparingInt(pendingArea -> pendingArea.mMinLayer)); + for (int i = 0; i < mChildren.size(); i++) { + final PendingArea child = mChildren.get(i); + final DisplayArea area = child.createArea(parent, areaForLayer); + parent.addChild(area, WindowContainer.POSITION_TOP); + if (mFeature != null) { + areas.get(mFeature).add(area); + } + child.instantiateChildren(area, areaForLayer, level + 1, areas); + } + } + + private DisplayArea createArea(DisplayArea<DisplayArea> parent, + DisplayArea.Tokens[] areaForLayer) { + if (mExisting != null) { + return mExisting; + } + DisplayArea.Type type; + if (mMinLayer > APPLICATION_LAYER) { + type = DisplayArea.Type.ABOVE_TASKS; + } else if (mMaxLayer < APPLICATION_LAYER) { + type = DisplayArea.Type.BELOW_TASKS; + } else { + type = DisplayArea.Type.ANY; + } + if (mFeature == null) { + final DisplayArea.Tokens leaf = new DisplayArea.Tokens(parent.mWmService, type, + "Leaf:" + mMinLayer + ":" + mMaxLayer); + for (int i = mMinLayer; i <= mMaxLayer; i++) { + areaForLayer[i] = leaf; + } + return leaf; + } else { + return new DisplayArea(parent.mWmService, type, mFeature.mName + ":" + + mMinLayer + ":" + mMaxLayer); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2196d899406d..e3d85c84b50c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2008,10 +2008,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getStackAt(stackNdx); stack.switchUser(userId); - Task task = stack.getTopMostTask(); - if (task != null && task != stack) { - stack.positionChildAtTop(task); - } } } diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index 2dde0bac74a8..f715d8f3529b 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -59,6 +59,7 @@ class TaskChangeNotificationController { private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 24; private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 25; private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 26; + private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 27; // Delay in notifying task stack change listeners (in millis) private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100; @@ -179,6 +180,10 @@ class TaskChangeNotificationController { l.onRecentTaskListFrozenChanged(m.arg1 != 0); }; + private final TaskStackConsumer mNotifyTaskFocusChanged = (l, m) -> { + l.onTaskFocusChanged(m.arg1, m.arg2 != 0); + }; + @FunctionalInterface public interface TaskStackConsumer { void accept(ITaskStackListener t, Message m) throws RemoteException; @@ -273,6 +278,9 @@ class TaskChangeNotificationController { case NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG: forAllRemoteListeners(mNotifyTaskListFrozen, msg); break; + case NOTIFY_TASK_FOCUS_CHANGED_MSG: + forAllRemoteListeners(mNotifyTaskFocusChanged, msg); + break; } } } @@ -565,4 +573,12 @@ class TaskChangeNotificationController { forAllLocalListeners(mNotifyTaskListFrozen, msg); msg.sendToTarget(); } + + /** @see ITaskStackListener#onTaskFocusChanged(int, boolean) */ + void notifyTaskFocusChanged(int taskId, boolean focused) { + final Message msg = mHandler.obtainMessage(NOTIFY_TASK_FOCUS_CHANGED_MSG, + taskId, focused ? 1 : 0); + forAllLocalListeners(mNotifyTaskFocusChanged, msg); + msg.sendToTarget(); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8f9caea26534..2bb67035f44b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7317,9 +7317,11 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG_WM, "updateInputMethodTargetWindow: imeToken=" + imeToken + " imeTargetWindowToken=" + imeTargetWindowToken); } - final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); - if (imeTarget != null) { - imeTarget.getDisplayContent().updateImeControlTarget(imeTarget); + synchronized (mGlobalLock) { + final WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); + if (imeTarget != null) { + imeTarget.getDisplayContent().updateImeControlTarget(imeTarget); + } } } diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index 7e6f79f0d407..70a9c09c815d 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -18,18 +18,27 @@ #define LOG_TAG "PackageManagerShellCommandDataLoader-jni" #include <android-base/logging.h> +#include <android-base/file.h> +#include <android-base/stringprintf.h> #include <android-base/unique_fd.h> -#include <nativehelper/JNIHelp.h> -#include "android-base/file.h" +#include <cutils/trace.h> +#include <sys/eventfd.h> +#include <sys/poll.h> -#include <endian.h> +#include <nativehelper/JNIHelp.h> #include <core_jni_helpers.h> +#include <endian.h> #include "dataloader.h" +#include <charconv> #include <chrono> +#include <span> +#include <string> #include <thread> +#include <unordered_map> +#include <unordered_set> namespace android { @@ -39,9 +48,26 @@ using android::base::borrowed_fd; using android::base::ReadFully; using android::base::unique_fd; +using namespace std::literals; + +using BlockSize = int16_t; +using FileIdx = int16_t; +using BlockIdx = int32_t; +using NumBlocks = int32_t; +using CompressionType = int16_t; +using RequestType = int16_t; +using MagicType = uint32_t; + static constexpr int BUFFER_SIZE = 256 * 1024; static constexpr int BLOCKS_COUNT = BUFFER_SIZE / INCFS_DATA_FILE_BLOCK_SIZE; +static constexpr int COMMAND_SIZE = 4 + 2 + 2 + 4; // bytes +static constexpr int HEADER_SIZE = 2 + 2 + 4 + 2; // bytes +static constexpr std::string_view OKAY = "OKAY"sv; +static constexpr MagicType INCR = 0x52434e49; // BE INCR + +static constexpr auto PollTimeoutMs = 5000; + struct JniIds { jclass packageManagerShellCommandDataLoader; jmethodID pmscdLookupShellCommand; @@ -85,6 +111,70 @@ const JniIds& jniIds(JNIEnv* env) { return ids; } +struct BlockHeader { + FileIdx fileIdx = -1; + CompressionType compressionType = -1; + BlockIdx blockIdx = -1; + BlockSize blockSize = -1; +} __attribute__((packed)); + +static_assert(sizeof(BlockHeader) == HEADER_SIZE); + +static constexpr RequestType EXIT = 0; +static constexpr RequestType BLOCK_MISSING = 1; +static constexpr RequestType PREFETCH = 2; + +struct RequestCommand { + MagicType magic; + RequestType requestType; + FileIdx fileIdx; + BlockIdx blockIdx; +} __attribute__((packed)); + +static_assert(COMMAND_SIZE == sizeof(RequestCommand)); + +static bool sendRequest(int fd, RequestType requestType, FileIdx fileIdx = -1, + BlockIdx blockIdx = -1) { + const RequestCommand command{.magic = INCR, + .requestType = static_cast<int16_t>(be16toh(requestType)), + .fileIdx = static_cast<int16_t>(be16toh(fileIdx)), + .blockIdx = static_cast<int32_t>(be32toh(blockIdx))}; + return android::base::WriteFully(fd, &command, sizeof(command)); +} + +static int waitForDataOrSignal(int fd, int event_fd) { + struct pollfd pfds[2] = {{fd, POLLIN, 0}, {event_fd, POLLIN, 0}}; + // Wait indefinitely until either data is ready or stop signal is received + int res = poll(pfds, 2, PollTimeoutMs); + if (res <= 0) { + return res; + } + // First check if there is a stop signal + if (pfds[1].revents == POLLIN) { + return event_fd; + } + // Otherwise check if incoming data is ready + if (pfds[0].revents == POLLIN) { + return fd; + } + return -1; +} + +static bool readChunk(int fd, std::vector<uint8_t>& data) { + int32_t size; + if (!android::base::ReadFully(fd, &size, sizeof(size))) { + return false; + } + size = int32_t(be32toh(size)); + if (size <= 0) { + return false; + } + data.resize(size); + return android::base::ReadFully(fd, data.data(), data.size()); +} + +BlockHeader readHeader(std::span<uint8_t>& data); + static inline int32_t readBEInt32(borrowed_fd fd) { int32_t result; ReadFully(fd, &result, sizeof(result)); @@ -106,6 +196,22 @@ static inline int32_t skipIdSigHeaders(borrowed_fd fd) { return readBEInt32(fd); // size of the verity tree } +static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) { + constexpr int SHA256_DIGEST_SIZE = 32; + constexpr int digest_size = SHA256_DIGEST_SIZE; + constexpr int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size; + + IncFsSize total_tree_block_count = 0; + + auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE; + auto hash_block_count = block_count; + for (auto i = 0; hash_block_count > 1; i++) { + hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block; + total_tree_block_count += hash_block_count; + } + return total_tree_block_count * INCFS_DATA_FILE_BLOCK_SIZE; +} + static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, jobject pfd) { if (!pfd) { ALOGE("Missing In ParcelFileDescriptor."); @@ -125,8 +231,9 @@ static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, job struct InputDesc { unique_fd fd; IncFsSize size; - IncFsBlockKind kind; - bool waitOnEof; + IncFsBlockKind kind = INCFS_BLOCK_KIND_DATA; + bool waitOnEof = false; + bool streaming = false; }; using InputDescs = std::vector<InputDesc>; @@ -135,8 +242,7 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel InputDescs result; result.reserve(2); - const bool fromStdin = (metadata.size == 0 || *metadata.data == '-'); - if (fromStdin) { + if (metadata.size == 0 || *metadata.data == '-') { // stdin auto fd = convertPfdToFdAndDup( env, jni, @@ -146,12 +252,29 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel result.push_back(InputDesc{ .fd = std::move(fd), .size = size, - .kind = INCFS_BLOCK_KIND_DATA, .waitOnEof = true, }); } return result; } + if (*metadata.data == '+') { + // verity tree from stdin, rest is streaming + auto fd = convertPfdToFdAndDup( + env, jni, + env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader, + jni.pmscdGetStdInPFD, shellCommand)); + if (fd.ok()) { + auto treeSize = verityTreeSizeForFile(size); + result.push_back(InputDesc{ + .fd = std::move(fd), + .size = treeSize, + .kind = INCFS_BLOCK_KIND_HASH, + .waitOnEof = true, + .streaming = true, + }); + } + return result; + } // local file and possibly signature const std::string filePath(metadata.data, metadata.size); @@ -163,13 +286,17 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel jni.pmscdGetLocalFile, shellCommand, env->NewStringUTF(idsigPath.c_str()))); if (idsigFd.ok()) { - ALOGE("idsig found, skipping to the tree"); - auto treeSize = skipIdSigHeaders(idsigFd); + auto treeSize = verityTreeSizeForFile(size); + auto actualTreeSize = skipIdSigHeaders(idsigFd); + if (treeSize != actualTreeSize) { + ALOGE("Verity tree size mismatch: %d vs .idsig: %d.", int(treeSize), + int(actualTreeSize)); + return {}; + } result.push_back(InputDesc{ .fd = std::move(idsigFd), .size = treeSize, .kind = INCFS_BLOCK_KIND_HASH, - .waitOnEof = false, }); } @@ -182,8 +309,6 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel result.push_back(InputDesc{ .fd = std::move(fileFd), .size = size, - .kind = INCFS_BLOCK_KIND_DATA, - .waitOnEof = false, }); } @@ -226,19 +351,32 @@ private: android::dataloader::StatusListenerPtr statusListener, android::dataloader::ServiceConnectorPtr, android::dataloader::ServiceParamsPtr) final { + CHECK(ifs) << "ifs can't be null"; + CHECK(statusListener) << "statusListener can't be null"; mArgs = params.arguments(); mIfs = ifs; + mStatusListener = statusListener; return true; } bool onStart() final { return true; } - void onStop() final {} - void onDestroy() final {} - - // IFS callbacks. - void onPendingReads(const dataloader::PendingReads& pendingReads) final {} - void onPageReads(const dataloader::PageReads& pageReads) final {} + void onStop() final { + mStopReceiving = true; + eventfd_write(mEventFd, 1); + if (mReceiverThread.joinable()) { + mReceiverThread.join(); + } + } + void onDestroy() final { + ALOGE("Sending EXIT to server."); + sendRequest(mOutFd, EXIT); + // Make sure the receiver thread stopped. + CHECK(!mReceiverThread.joinable()); + + mInFd.reset(); + mOutFd.reset(); + } - // FS callbacks. + // Installation. bool onPrepareImage(const dataloader::DataLoaderInstallationFiles& addedFiles) final { JNIEnv* env = GetOrAttachJNIEnvironment(mJvm); const auto& jni = jniIds(env); @@ -257,6 +395,7 @@ private: std::vector<IncFsDataBlock> blocks; blocks.reserve(BLOCKS_COUNT); + unique_fd streamingFd; for (auto&& file : addedFiles) { auto inputs = openInputs(env, jni, shellCommand, file.size, file.metadata); if (inputs.empty()) { @@ -267,7 +406,6 @@ private: } const auto fileId = IncFs_FileIdFromMetadata(file.metadata); - const auto incfsFd(mIfs->openWrite(fileId)); if (incfsFd < 0) { ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. " @@ -277,6 +415,9 @@ private: } for (auto&& input : inputs) { + if (input.streaming && !streamingFd.ok()) { + streamingFd.reset(dup(input.fd)); + } if (!copyToIncFs(incfsFd, input.size, input.kind, input.fd, input.waitOnEof, &buffer, &blocks)) { ALOGE("Failed to copy data to IncFS file for metadata: %.*s, final file name " @@ -288,7 +429,12 @@ private: } } - ALOGE("All done."); + if (streamingFd.ok()) { + ALOGE("onPrepareImage: done, proceeding to streaming."); + return initStreaming(std::move(streamingFd)); + } + + ALOGE("onPrepareImage: done."); return true; } @@ -378,11 +524,253 @@ private: return true; } + // Read tracing. + struct TracedRead { + uint64_t timestampUs; + android::dataloader::FileId fileId; + uint32_t firstBlockIdx; + uint32_t count; + }; + + void onPageReads(const android::dataloader::PageReads& pageReads) final { + auto trace = atrace_is_tag_enabled(ATRACE_TAG); + if (CC_LIKELY(!trace)) { + return; + } + + TracedRead last = {}; + for (auto&& read : pageReads) { + if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) { + traceRead(last); + last = TracedRead{ + .timestampUs = read.bootClockTsUs, + .fileId = read.id, + .firstBlockIdx = (uint32_t)read.block, + .count = 1, + }; + } else { + ++last.count; + } + } + traceRead(last); + } + + void traceRead(const TracedRead& read) { + if (!read.count) { + return; + } + + FileIdx fileIdx = convertFileIdToFileIndex(read.fileId); + auto str = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d", + static_cast<long long>(read.firstBlockIdx), + static_cast<long long>(read.count), + static_cast<int>(fileIdx)); + ATRACE_BEGIN(str.c_str()); + ATRACE_END(); + } + + // Streaming. + bool initStreaming(unique_fd inout) { + mInFd.reset(dup(inout)); + mOutFd.reset(dup(inout)); + if (mInFd < 0 || mOutFd < 0) { + ALOGE("Failed to dup FDs."); + return false; + } + + mEventFd.reset(eventfd(0, EFD_CLOEXEC)); + if (mEventFd < 0) { + ALOGE("Failed to create eventfd."); + return false; + } + + // Awaiting adb handshake. + char okay_buf[OKAY.size()]; + if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) { + ALOGE("Failed to receive OKAY. Abort."); + return false; + } + if (std::string_view(okay_buf, OKAY.size()) != OKAY) { + ALOGE("Received '%.*s', expecting '%.*s'", (int)OKAY.size(), okay_buf, (int)OKAY.size(), + OKAY.data()); + return false; + } + + mReceiverThread = std::thread([this]() { receiver(); }); + ALOGI("Started streaming..."); + return true; + } + + // IFS callbacks. + void onPendingReads(const dataloader::PendingReads& pendingReads) final { + CHECK(mIfs); + for (auto&& pendingRead : pendingReads) { + const android::dataloader::FileId& fileId = pendingRead.id; + const auto blockIdx = static_cast<BlockIdx>(pendingRead.block); + /* + ALOGI("Missing: %d", (int) blockIdx); + */ + FileIdx fileIdx = convertFileIdToFileIndex(fileId); + if (fileIdx < 0) { + ALOGE("Failed to handle event for fileid=%s. Ignore.", + android::incfs::toString(fileId).c_str()); + continue; + } + if (mRequestedFiles.insert(fileIdx).second) { + if (!sendRequest(mOutFd, PREFETCH, fileIdx, blockIdx)) { + ALOGE("Failed to request prefetch for fileid=%s. Ignore.", + android::incfs::toString(fileId).c_str()); + mRequestedFiles.erase(fileIdx); + mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION); + } + } + sendRequest(mOutFd, BLOCK_MISSING, fileIdx, blockIdx); + } + } + + void receiver() { + std::vector<uint8_t> data; + std::vector<IncFsDataBlock> instructions; + std::unordered_map<FileIdx, unique_fd> writeFds; + while (!mStopReceiving) { + const int res = waitForDataOrSignal(mInFd, mEventFd); + if (res == 0) { + continue; + } + if (res < 0) { + ALOGE("Failed to poll. Abort."); + mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION); + break; + } + if (res == mEventFd) { + ALOGE("Received stop signal. Exit."); + break; + } + if (!readChunk(mInFd, data)) { + ALOGE("Failed to read a message. Abort."); + mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION); + break; + } + auto remainingData = std::span(data); + while (!remainingData.empty()) { + auto header = readHeader(remainingData); + if (header.fileIdx == -1 && header.compressionType == 0 && header.blockIdx == 0 && + header.blockSize == 0) { + ALOGI("Stop signal received. Sending exit command (remaining bytes: %d).", + int(remainingData.size())); + + sendRequest(mOutFd, EXIT); + mStopReceiving = true; + break; + } + if (header.fileIdx < 0 || header.blockSize <= 0 || header.compressionType < 0 || + header.blockIdx < 0) { + ALOGE("invalid header received. Abort."); + mStopReceiving = true; + break; + } + const FileIdx fileIdx = header.fileIdx; + const android::dataloader::FileId fileId = convertFileIndexToFileId(fileIdx); + if (!android::incfs::isValidFileId(fileId)) { + ALOGE("Unknown data destination for file ID %d. " + "Ignore.", + header.fileIdx); + continue; + } + + auto& writeFd = writeFds[fileIdx]; + if (writeFd < 0) { + writeFd = this->mIfs->openWrite(fileId); + if (writeFd < 0) { + ALOGE("Failed to open file %d for writing (%d). Aboring.", header.fileIdx, + -writeFd); + break; + } + } + + const auto inst = IncFsDataBlock{ + .fileFd = writeFd, + .pageIndex = static_cast<IncFsBlockIndex>(header.blockIdx), + .compression = static_cast<IncFsCompressionKind>(header.compressionType), + .kind = INCFS_BLOCK_KIND_DATA, + .dataSize = static_cast<uint16_t>(header.blockSize), + .data = (const char*)remainingData.data(), + }; + instructions.push_back(inst); + remainingData = remainingData.subspan(header.blockSize); + } + writeInstructions(instructions); + } + writeInstructions(instructions); + } + + void writeInstructions(std::vector<IncFsDataBlock>& instructions) { + auto res = this->mIfs->writeBlocks(instructions); + if (res != instructions.size()) { + ALOGE("Dailed to write data to Incfs (res=%d when expecting %d)", res, + int(instructions.size())); + } + instructions.clear(); + } + + FileIdx convertFileIdToFileIndex(android::dataloader::FileId fileId) { + // FileId is a string in format '+FileIdx\0'. + const char* meta = (const char*)&fileId; + if (*meta != '+') { + return -1; + } + + int fileIdx; + auto res = std::from_chars(meta + 1, meta + sizeof(fileId), fileIdx); + if (res.ec != std::errc{} || fileIdx < std::numeric_limits<FileIdx>::min() || + fileIdx > std::numeric_limits<FileIdx>::max()) { + return -1; + } + + return FileIdx(fileIdx); + } + + android::dataloader::FileId convertFileIndexToFileId(FileIdx fileIdx) { + IncFsFileId fileId = {}; + char* meta = (char*)&fileId; + *meta = '+'; + if (auto [p, ec] = std::to_chars(meta + 1, meta + sizeof(fileId), fileIdx); + ec != std::errc()) { + return {}; + } + return fileId; + } + JavaVM* const mJvm; std::string mArgs; - android::dataloader::FilesystemConnectorPtr mIfs; + android::dataloader::FilesystemConnectorPtr mIfs = nullptr; + android::dataloader::StatusListenerPtr mStatusListener = nullptr; + android::base::unique_fd mInFd; + android::base::unique_fd mOutFd; + android::base::unique_fd mEventFd; + std::thread mReceiverThread; + std::atomic<bool> mStopReceiving = false; + /** Tracks which files have been requested */ + std::unordered_set<FileIdx> mRequestedFiles; }; +BlockHeader readHeader(std::span<uint8_t>& data) { + BlockHeader header; + if (data.size() < sizeof(header)) { + return header; + } + + header.fileIdx = static_cast<FileIdx>(be16toh(*reinterpret_cast<const uint16_t*>(&data[0]))); + header.compressionType = + static_cast<CompressionType>(be16toh(*reinterpret_cast<const uint16_t*>(&data[2]))); + header.blockIdx = static_cast<BlockIdx>(be32toh(*reinterpret_cast<const uint32_t*>(&data[4]))); + header.blockSize = + static_cast<BlockSize>(be16toh(*reinterpret_cast<const uint16_t*>(&data[8]))); + data = data.subspan(sizeof(header)); + + return header; +} + static void nativeInitialize(JNIEnv* env, jclass klass) { jniIds(env); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 93662c91af90..1936f13ca6e1 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1741,7 +1741,7 @@ public final class SystemServer { mSystemServiceManager.startService(SensorNotificationService.class); t.traceEnd(); - if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONTEXTHUB)) { + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONTEXT_HUB)) { t.traceBegin("StartContextHubSystemService"); mSystemServiceManager.startService(ContextHubSystemService.class); t.traceEnd(); diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 2d18a2994135..663bf4f30708 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -17,6 +17,7 @@ package com.android.server.people; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.AppTarget; @@ -24,6 +25,7 @@ import android.app.prediction.AppTargetEvent; import android.app.prediction.IPredictionCallback; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.os.CancellationSignal; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; @@ -138,6 +140,21 @@ public class PeopleService extends SystemService { }); } + @Override + public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) { + mDataManager.pruneDataForUser(userId, signal); + } + + @Override + public byte[] backupConversationInfos(@UserIdInt int userId) { + return new byte[0]; + } + + @Override + public void restoreConversationInfos(@UserIdInt int userId, @NonNull String key, + @NonNull byte[] payload) { + } + @VisibleForTesting SessionInfo getSessionInfo(AppPredictionSessionId sessionId) { return mSessions.get(sessionId); @@ -160,14 +177,5 @@ public class PeopleService extends SystemService { Slog.e(TAG, "Failed to calling callback" + e); } } - - @Override - public byte[] backupConversationInfos(int userId) { - return new byte[0]; - } - - @Override - public void restoreConversationInfos(int userId, String key, byte[] payload) { - } } } diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java index ea36d38e5d4a..3afb209ae5cd 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -133,10 +133,11 @@ class ConversationStore { } @MainThread - synchronized void deleteConversation(@NonNull String shortcutId) { + @Nullable + synchronized ConversationInfo deleteConversation(@NonNull String shortcutId) { ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId); if (conversationInfo == null) { - return; + return null; } LocusId locusId = conversationInfo.getLocusId(); @@ -159,6 +160,7 @@ class ConversationStore { mNotifChannelIdToShortcutIdMap.remove(notifChannelId); } scheduleUpdateConversationsOnDisk(); + return conversationInfo; } synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { diff --git a/services/people/java/com/android/server/people/data/DataMaintenanceService.java b/services/people/java/com/android/server/people/data/DataMaintenanceService.java new file mode 100644 index 000000000000..58f0654c7b51 --- /dev/null +++ b/services/people/java/com/android/server/people/data/DataMaintenanceService.java @@ -0,0 +1,92 @@ +/* + * 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.server.people.data; + +import android.annotation.UserIdInt; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.CancellationSignal; + +import com.android.server.LocalServices; +import com.android.server.people.PeopleServiceInternal; + +import java.util.concurrent.TimeUnit; + +/** + * This service runs periodically to ensure the data consistency and the retention policy for a + * specific user's data. + */ +public class DataMaintenanceService extends JobService { + + private static final long JOB_RUN_INTERVAL = TimeUnit.HOURS.toMillis(24); + + /** This job ID must be unique within the system server. */ + private static final int BASE_JOB_ID = 0xC315BD7; // 204561367 + + static void scheduleJob(Context context, @UserIdInt int userId) { + int jobId = getJobId(userId); + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + if (jobScheduler.getPendingJob(jobId) == null) { + ComponentName component = new ComponentName(context, DataMaintenanceService.class); + JobInfo newJob = new JobInfo.Builder(jobId, component) + .setRequiresDeviceIdle(true) + .setPeriodic(JOB_RUN_INTERVAL) + .build(); + jobScheduler.schedule(newJob); + } + } + + static void cancelJob(Context context, @UserIdInt int userId) { + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + jobScheduler.cancel(getJobId(userId)); + } + + private CancellationSignal mSignal; + + @Override + public boolean onStartJob(JobParameters params) { + int userId = getUserId(params.getJobId()); + mSignal = new CancellationSignal(); + new Thread(() -> { + PeopleServiceInternal peopleServiceInternal = + LocalServices.getService(PeopleServiceInternal.class); + peopleServiceInternal.pruneDataForUser(userId, mSignal); + jobFinished(params, mSignal.isCanceled()); + }).start(); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + if (mSignal != null) { + mSignal.cancel(); + } + return false; + } + + private static int getJobId(@UserIdInt int userId) { + return BASE_JOB_ID + userId; + } + + private static @UserIdInt int getUserId(int jobId) { + return jobId - BASE_JOB_ID; + } +} diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index a904b42f47db..2edfd7487ec0 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.PackageManagerInternal; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.pm.ShortcutManager.ShareShortcutInfo; @@ -40,6 +41,7 @@ import android.content.pm.UserInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.CancellationSignal; import android.os.Handler; import android.os.Process; import android.os.RemoteException; @@ -53,16 +55,20 @@ import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; import android.text.TextUtils; import android.text.format.DateUtils; +import android.util.ArraySet; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ChooserActivity; +import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.telephony.SmsApplication; import com.android.server.LocalServices; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -94,10 +100,12 @@ public class DataManager { private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>(); private final SparseArray<NotificationListenerService> mNotificationListeners = new SparseArray<>(); + private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>(); private final ContentObserver mCallLogContentObserver; private final ContentObserver mMmsSmsContentObserver; private ShortcutServiceInternal mShortcutServiceInternal; + private PackageManagerInternal mPackageManagerInternal; private ShortcutManager mShortcutManager; private UserManager mUserManager; @@ -120,6 +128,7 @@ public class DataManager { /** Initialization. Called when the system services are up running. */ public void initialize() { mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mShortcutManager = mContext.getSystemService(ShortcutManager.class); mUserManager = mContext.getSystemService(UserManager.class); @@ -172,6 +181,10 @@ public class DataManager { // Should never occur for local calls. } + PackageMonitor packageMonitor = new PerUserPackageMonitor(); + packageMonitor.register(mContext, null, UserHandle.of(userId), true); + mPackageMonitors.put(userId, packageMonitor); + if (userId == UserHandle.USER_SYSTEM) { // The call log and MMS/SMS messages are shared across user profiles. So only need to // register the content observers once for the primary user. @@ -183,6 +196,8 @@ public class DataManager { MmsSms.CONTENT_URI, /* notifyForDescendants= */ false, mMmsSmsContentObserver, UserHandle.USER_SYSTEM); } + + DataMaintenanceService.scheduleJob(mContext, userId); } /** This method is called when a user is stopped. */ @@ -207,10 +222,15 @@ public class DataManager { // Should never occur for local calls. } } + if (mPackageMonitors.indexOfKey(userId) >= 0) { + mPackageMonitors.get(userId).unregister(); + } if (userId == UserHandle.USER_SYSTEM) { mContext.getContentResolver().unregisterContentObserver(mCallLogContentObserver); mContext.getContentResolver().unregisterContentObserver(mMmsSmsContentObserver); } + + DataMaintenanceService.cancelJob(mContext, userId); } /** @@ -274,9 +294,8 @@ public class DataManager { || TextUtils.isEmpty(mimeType)) { return; } - EventHistoryImpl eventHistory = - packageData.getEventStore().getOrCreateShortcutEventHistory( - shortcutInfo.getId()); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, shortcutInfo.getId()); @Event.EventType int eventType; if (mimeType.startsWith("text/")) { eventType = Event.TYPE_SHARE_TEXT; @@ -291,6 +310,45 @@ public class DataManager { } } + /** Prunes the data for the specified user. */ + public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) { + UserData userData = getUnlockedUserData(userId); + if (userData == null || signal.isCanceled()) { + return; + } + pruneUninstalledPackageData(userData); + + long currentTimeMillis = System.currentTimeMillis(); + userData.forAllPackages(packageData -> { + if (signal.isCanceled()) { + return; + } + packageData.getEventStore().pruneOldEvents(currentTimeMillis); + if (!packageData.isDefaultDialer()) { + packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_CALL); + } + if (!packageData.isDefaultSmsApp()) { + packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS); + } + packageData.pruneOrphanEvents(); + }); + } + + private void pruneUninstalledPackageData(@NonNull UserData userData) { + Set<String> installApps = new ArraySet<>(); + mPackageManagerInternal.forEachInstalledPackage( + pkg -> installApps.add(pkg.getPackageName()), userData.getUserId()); + List<String> packagesToDelete = new ArrayList<>(); + userData.forAllPackages(packageData -> { + if (!installApps.contains(packageData.getPackageName())) { + packagesToDelete.add(packageData.getPackageName()); + } + }); + for (String packageName : packagesToDelete) { + userData.deletePackageData(packageName); + } + } + /** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */ private List<ShortcutInfo> getShortcuts( @NonNull String packageName, @UserIdInt int userId, @@ -347,7 +405,8 @@ public class DataManager { || packageData.getConversationStore().getConversation(shortcutId) == null) { return null; } - return packageData.getEventStore().getOrCreateShortcutEventHistory(shortcutId); + return packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); } @VisibleForTesting @@ -413,6 +472,11 @@ public class DataManager { } @VisibleForTesting + PackageMonitor getPackageMonitorForTesting(@UserIdInt int userId) { + return mPackageMonitors.get(userId); + } + + @VisibleForTesting UserData getUserDataForTesting(@UserIdInt int userId) { return mUserDataArray.get(userId); } @@ -499,7 +563,8 @@ public class DataManager { return; } EventStore eventStore = defaultDialer.getEventStore(); - eventStore.getOrCreateCallEventHistory(phoneNumber).addEvent(event); + eventStore.getOrCreateEventHistory( + EventStore.CATEGORY_CALL, phoneNumber).addEvent(event); }); } } @@ -544,7 +609,8 @@ public class DataManager { return; } EventStore eventStore = defaultSmsApp.getEventStore(); - eventStore.getOrCreateSmsEventHistory(phoneNumber).addEvent(event); + eventStore.getOrCreateEventHistory( + EventStore.CATEGORY_SMS, phoneNumber).addEvent(event); }); } } @@ -670,6 +736,20 @@ public class DataManager { } } + private class PerUserPackageMonitor extends PackageMonitor { + + @Override + public void onPackageRemoved(String packageName, int uid) { + super.onPackageRemoved(packageName, uid); + + int userId = getChangingUserId(); + UserData userData = getUnlockedUserData(userId); + if (userData != null) { + userData.deletePackageData(packageName); + } + } + } + private class ShutdownBroadcastReceiver extends BroadcastReceiver { @Override diff --git a/services/people/java/com/android/server/people/data/EventHistoryImpl.java b/services/people/java/com/android/server/people/data/EventHistoryImpl.java index 6b6bd7e3cfb0..6bef1db5582e 100644 --- a/services/people/java/com/android/server/people/data/EventHistoryImpl.java +++ b/services/people/java/com/android/server/people/data/EventHistoryImpl.java @@ -78,6 +78,17 @@ class EventHistoryImpl implements EventHistory { mRecentEvents.add(event); } + void onDestroy() { + mEventIndexArray.clear(); + mRecentEvents.clear(); + // TODO: STOPSHIP: Delete the data files. + } + + /** Deletes the events data that exceeds the retention period. */ + void pruneOldEvents(long currentTimeMillis) { + // TODO: STOPSHIP: Delete the old events data files. + } + @VisibleForTesting static class Injector { diff --git a/services/people/java/com/android/server/people/data/EventList.java b/services/people/java/com/android/server/people/data/EventList.java index b267d667b422..d770f912ea40 100644 --- a/services/people/java/com/android/server/people/data/EventList.java +++ b/services/people/java/com/android/server/people/data/EventList.java @@ -69,6 +69,10 @@ class EventList { return result; } + void clear() { + mEvents.clear(); + } + /** Returns the first index whose timestamp is greater or equal to the provided timestamp. */ private int firstIndexOnOrAfter(long timestamp) { int result = mEvents.size(); diff --git a/services/people/java/com/android/server/people/data/EventStore.java b/services/people/java/com/android/server/people/data/EventStore.java index d6b7a863ca2d..c8d44ac07106 100644 --- a/services/people/java/com/android/server/people/data/EventStore.java +++ b/services/people/java/com/android/server/people/data/EventStore.java @@ -16,99 +16,129 @@ package com.android.server.people.data; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.LocusId; import android.util.ArrayMap; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; /** The store that stores and accesses the events data for a package. */ class EventStore { - private final EventHistoryImpl mPackageEventHistory = new EventHistoryImpl(); + /** The events that are queryable with a shortcut ID. */ + static final int CATEGORY_SHORTCUT_BASED = 0; - // Shortcut ID -> Event History - private final Map<String, EventHistoryImpl> mShortcutEventHistoryMap = new ArrayMap<>(); + /** The events that are queryable with a {@link android.content.LocusId}. */ + static final int CATEGORY_LOCUS_ID_BASED = 1; - // Locus ID -> Event History - private final Map<LocusId, EventHistoryImpl> mLocusEventHistoryMap = new ArrayMap<>(); + /** The phone call events that are queryable with a phone number. */ + static final int CATEGORY_CALL = 2; - // Phone Number -> Event History - private final Map<String, EventHistoryImpl> mCallEventHistoryMap = new ArrayMap<>(); + /** The SMS or MMS events that are queryable with a phone number. */ + static final int CATEGORY_SMS = 3; - // Phone Number -> Event History - private final Map<String, EventHistoryImpl> mSmsEventHistoryMap = new ArrayMap<>(); + /** The events that are queryable with an {@link android.app.Activity} class name. */ + static final int CATEGORY_CLASS_BASED = 4; - /** Gets the package level {@link EventHistory}. */ - @NonNull - EventHistory getPackageEventHistory() { - return mPackageEventHistory; - } + @IntDef(prefix = { "CATEGORY_" }, value = { + CATEGORY_SHORTCUT_BASED, + CATEGORY_LOCUS_ID_BASED, + CATEGORY_CALL, + CATEGORY_SMS, + CATEGORY_CLASS_BASED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface EventCategory {} - /** Gets the {@link EventHistory} for the specified {@code shortcutId} if exists. */ - @Nullable - EventHistory getShortcutEventHistory(String shortcutId) { - return mShortcutEventHistoryMap.get(shortcutId); - } + private final List<Map<String, EventHistoryImpl>> mEventHistoryMaps = new ArrayList<>(); - /** Gets the {@link EventHistory} for the specified {@code locusId} if exists. */ - @Nullable - EventHistory getLocusEventHistory(LocusId locusId) { - return mLocusEventHistoryMap.get(locusId); + EventStore() { + mEventHistoryMaps.add(CATEGORY_SHORTCUT_BASED, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_LOCUS_ID_BASED, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_CALL, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_SMS, new ArrayMap<>()); + mEventHistoryMaps.add(CATEGORY_CLASS_BASED, new ArrayMap<>()); } - /** Gets the phone call {@link EventHistory} for the specified {@code phoneNumber} if exists. */ - @Nullable - EventHistory getCallEventHistory(String phoneNumber) { - return mCallEventHistoryMap.get(phoneNumber); - } - - /** Gets the SMS {@link EventHistory} for the specified {@code phoneNumber} if exists. */ + /** + * Gets the {@link EventHistory} for the specified key if exists. + * + * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class + * name. + */ @Nullable - EventHistory getSmsEventHistory(String phoneNumber) { - return mSmsEventHistoryMap.get(phoneNumber); + EventHistory getEventHistory(@EventCategory int category, String key) { + return mEventHistoryMaps.get(category).get(key); } /** - * Gets the {@link EventHistoryImpl} for the specified {@code shortcutId} or creates a new - * instance and put it into the store if not exists. The caller needs to verify if a - * conversation with this shortcut ID exists before calling this method. + * Gets the {@link EventHistoryImpl} for the specified ID or creates a new instance and put it + * into the store if not exists. The caller needs to verify if the associated conversation + * exists before calling this method. + * + * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class + * name. */ @NonNull - EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) { - return mShortcutEventHistoryMap.computeIfAbsent(shortcutId, key -> new EventHistoryImpl()); + EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) { + return mEventHistoryMaps.get(category).computeIfAbsent(key, k -> new EventHistoryImpl()); } /** - * Gets the {@link EventHistoryImpl} for the specified {@code locusId} or creates a new - * instance and put it into the store if not exists. The caller needs to ensure a conversation - * with this locus ID exists before calling this method. + * Deletes the events and index data for the specified key. + * + * @param key Category-specific key, it can be shortcut ID, locus ID, phone number, or class + * name. */ - @NonNull - EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) { - return mLocusEventHistoryMap.computeIfAbsent(locusId, key -> new EventHistoryImpl()); + void deleteEventHistory(@EventCategory int category, String key) { + EventHistoryImpl eventHistory = mEventHistoryMaps.get(category).remove(key); + if (eventHistory != null) { + eventHistory.onDestroy(); + } } - /** - * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for call events - * or creates a new instance and put it into the store if not exists. The caller needs to ensure - * a conversation with this phone number exists and this package is the default dialer - * before calling this method. - */ - @NonNull - EventHistoryImpl getOrCreateCallEventHistory(String phoneNumber) { - return mCallEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl()); + /** Deletes all the events and index data for the specified category from disk. */ + void deleteEventHistories(@EventCategory int category) { + mEventHistoryMaps.get(category).clear(); + // TODO: Implement this method to delete the data from disk. + } + + /** Deletes the events data that exceeds the retention period. */ + void pruneOldEvents(long currentTimeMillis) { + for (Map<String, EventHistoryImpl> map : mEventHistoryMaps) { + for (EventHistoryImpl eventHistory : map.values()) { + eventHistory.pruneOldEvents(currentTimeMillis); + } + } } /** - * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for SMS events - * or creates a new instance and put it into the store if not exists. The caller needs to ensure - * a conversation with this phone number exists and this package is the default SMS app - * before calling this method. + * Prunes the event histories whose key (shortcut ID, locus ID or phone number) does not match + * any conversations. + * + * @param keyChecker Check whether there exists a conversation contains this key. */ - @NonNull - EventHistoryImpl getOrCreateSmsEventHistory(String phoneNumber) { - return mSmsEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl()); + void pruneOrphanEventHistories(@EventCategory int category, Predicate<String> keyChecker) { + Set<String> keys = mEventHistoryMaps.get(category).keySet(); + List<String> keysToDelete = new ArrayList<>(); + for (String key : keys) { + if (!keyChecker.test(key)) { + keysToDelete.add(key); + } + } + Map<String, EventHistoryImpl> eventHistoryMap = mEventHistoryMaps.get(category); + for (String key : keysToDelete) { + EventHistoryImpl eventHistory = eventHistoryMap.remove(key); + if (eventHistory != null) { + eventHistory.onDestroy(); + } + } } } diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java index f67699c28531..c55f97205bc5 100644 --- a/services/people/java/com/android/server/people/data/PackageData.java +++ b/services/people/java/com/android/server/people/data/PackageData.java @@ -16,6 +16,12 @@ package com.android.server.people.data; +import static com.android.server.people.data.EventStore.CATEGORY_CALL; +import static com.android.server.people.data.EventStore.CATEGORY_CLASS_BASED; +import static com.android.server.people.data.EventStore.CATEGORY_LOCUS_ID_BASED; +import static com.android.server.people.data.EventStore.CATEGORY_SHORTCUT_BASED; +import static com.android.server.people.data.EventStore.CATEGORY_SMS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -89,11 +95,6 @@ public class PackageData { mConversationStore.forAllConversations(consumer); } - @NonNull - public EventHistory getPackageLevelEventHistory() { - return getEventStore().getPackageEventHistory(); - } - /** * Gets the {@link ConversationInfo} for a given shortcut ID. Returns null if such as {@link * ConversationInfo} does not exist. @@ -117,14 +118,16 @@ public class PackageData { return result; } - EventHistory shortcutEventHistory = getEventStore().getShortcutEventHistory(shortcutId); + EventHistory shortcutEventHistory = getEventStore().getEventHistory( + CATEGORY_SHORTCUT_BASED, shortcutId); if (shortcutEventHistory != null) { result.addEventHistory(shortcutEventHistory); } LocusId locusId = conversationInfo.getLocusId(); if (locusId != null) { - EventHistory locusEventHistory = getEventStore().getLocusEventHistory(locusId); + EventHistory locusEventHistory = getEventStore().getEventHistory( + CATEGORY_LOCUS_ID_BASED, locusId.getId()); if (locusEventHistory != null) { result.addEventHistory(locusEventHistory); } @@ -135,13 +138,15 @@ public class PackageData { return result; } if (isDefaultDialer()) { - EventHistory callEventHistory = getEventStore().getCallEventHistory(phoneNumber); + EventHistory callEventHistory = getEventStore().getEventHistory( + CATEGORY_CALL, phoneNumber); if (callEventHistory != null) { result.addEventHistory(callEventHistory); } } if (isDefaultSmsApp()) { - EventHistory smsEventHistory = getEventStore().getSmsEventHistory(phoneNumber); + EventHistory smsEventHistory = getEventStore().getEventHistory( + CATEGORY_SMS, phoneNumber); if (smsEventHistory != null) { result.addEventHistory(smsEventHistory); } @@ -149,6 +154,14 @@ public class PackageData { return result; } + /** Gets the {@link EventHistory} for a given Activity class. */ + @NonNull + public EventHistory getClassLevelEventHistory(String className) { + EventHistory eventHistory = getEventStore().getEventHistory( + CATEGORY_CLASS_BASED, className); + return eventHistory != null ? eventHistory : new AggregateEventHistoryImpl(); + } + public boolean isDefaultDialer() { return mIsDefaultDialerPredicate.test(mPackageName); } @@ -167,6 +180,47 @@ public class PackageData { return mEventStore; } + /** + * Deletes all the data (including conversation, events and index) for the specified + * conversation shortcut ID. + */ + void deleteDataForConversation(String shortcutId) { + ConversationInfo conversationInfo = mConversationStore.deleteConversation(shortcutId); + if (conversationInfo == null) { + return; + } + mEventStore.deleteEventHistory(CATEGORY_SHORTCUT_BASED, shortcutId); + if (conversationInfo.getLocusId() != null) { + mEventStore.deleteEventHistory( + CATEGORY_LOCUS_ID_BASED, conversationInfo.getLocusId().getId()); + } + String phoneNumber = conversationInfo.getContactPhoneNumber(); + if (!TextUtils.isEmpty(phoneNumber)) { + if (isDefaultDialer()) { + mEventStore.deleteEventHistory(CATEGORY_CALL, phoneNumber); + } + if (isDefaultSmsApp()) { + mEventStore.deleteEventHistory(CATEGORY_SMS, phoneNumber); + } + } + } + + /** Prunes the events and index data that don't have a associated conversation. */ + void pruneOrphanEvents() { + mEventStore.pruneOrphanEventHistories(CATEGORY_SHORTCUT_BASED, + key -> mConversationStore.getConversation(key) != null); + mEventStore.pruneOrphanEventHistories(CATEGORY_LOCUS_ID_BASED, + key -> mConversationStore.getConversationByLocusId(new LocusId(key)) != null); + if (isDefaultDialer()) { + mEventStore.pruneOrphanEventHistories(CATEGORY_CALL, + key -> mConversationStore.getConversationByPhoneNumber(key) != null); + } + if (isDefaultSmsApp()) { + mEventStore.pruneOrphanEventHistories(CATEGORY_SMS, + key -> mConversationStore.getConversationByPhoneNumber(key) != null); + } + } + void onDestroy() { // TODO: STOPSHIP: Implements this method for the case of package being uninstalled. } diff --git a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java index 644c155b4158..6dcfaa00dfd5 100644 --- a/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java +++ b/services/people/java/com/android/server/people/data/UsageStatsQueryHelper.java @@ -59,7 +59,7 @@ class UsageStatsQueryHelper { */ boolean querySince(long sinceTime) { UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( - mUserId, sinceTime, System.currentTimeMillis(), false, false, false); + mUserId, sinceTime, System.currentTimeMillis(), UsageEvents.SHOW_ALL_EVENT_DATA); if (usageEvents == null) { return false; } @@ -129,8 +129,8 @@ class UsageStatsQueryHelper { if (packageData.getConversationStore().getConversation(shortcutId) == null) { return; } - EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( - shortcutId); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); eventHistory.addEvent(event); } @@ -138,8 +138,8 @@ class UsageStatsQueryHelper { if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) { return; } - EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateLocusEventHistory( - locusId); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId()); eventHistory.addEvent(event); } @@ -151,8 +151,8 @@ class UsageStatsQueryHelper { if (conversationInfo == null) { return; } - EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateShortcutEventHistory( - conversationInfo.getShortcutId()); + EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( + EventStore.CATEGORY_SHORTCUT_BASED, conversationInfo.getShortcutId()); eventHistory.addEvent(event); } } diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java index aaa5db878e08..7ca4b6c76a36 100644 --- a/services/people/java/com/android/server/people/data/UserData.java +++ b/services/people/java/com/android/server/people/data/UserData.java @@ -104,6 +104,14 @@ class UserData { return mPackageDataMap.get(packageName); } + /** Deletes the specified package data. */ + void deletePackageData(@NonNull String packageName) { + PackageData packageData = mPackageDataMap.remove(packageName); + if (packageData != null) { + packageData.onDestroy(); + } + } + void setDefaultDialer(@Nullable String packageName) { mDefaultDialer = packageName; } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 64da6f6b8590..d7a3cfd8aeca 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -19,6 +19,12 @@ package com.android.server.job.controllers; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; +import static com.android.server.job.JobSchedulerService.NEVER_INDEX; +import static com.android.server.job.JobSchedulerService.RARE_INDEX; +import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; +import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; @@ -34,13 +40,16 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_WITHIN_QUO import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; import android.app.job.JobInfo; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.pm.PackageManagerInternal; +import android.net.Uri; import android.os.SystemClock; import android.provider.MediaStore; +import android.util.SparseIntArray; import androidx.test.runner.AndroidJUnit4; @@ -52,6 +61,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -61,7 +71,12 @@ import java.time.ZoneOffset; @RunWith(AndroidJUnit4.class) public class JobStatusTest { private static final double DELTA = 0.00001; + private static final String TEST_PACKAGE = "job.test.package"; + private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(TEST_PACKAGE, "test"); + private static final Uri TEST_MEDIA_URI = Uri.parse("content://media/path/to/media"); + @Mock + private JobSchedulerInternal mJobSchedulerInternal; private MockitoSession mMockingSession; @Before @@ -71,7 +86,7 @@ public class JobStatusTest { .strictness(Strictness.LENIENT) .mockStatic(LocalServices.class) .startMocking(); - doReturn(mock(JobSchedulerInternal.class)) + doReturn(mJobSchedulerInternal) .when(() -> LocalServices.getService(JobSchedulerInternal.class)); doReturn(mock(PackageManagerInternal.class)) .when(() -> LocalServices.getService(PackageManagerInternal.class)); @@ -94,6 +109,82 @@ public class JobStatusTest { } } + private static void assertEffectiveBucketForMediaExemption(JobStatus jobStatus, + boolean exemptionGranted) { + final SparseIntArray effectiveBucket = new SparseIntArray(); + effectiveBucket.put(ACTIVE_INDEX, ACTIVE_INDEX); + effectiveBucket.put(WORKING_INDEX, WORKING_INDEX); + effectiveBucket.put(FREQUENT_INDEX, exemptionGranted ? WORKING_INDEX : FREQUENT_INDEX); + effectiveBucket.put(RARE_INDEX, exemptionGranted ? WORKING_INDEX : RARE_INDEX); + effectiveBucket.put(NEVER_INDEX, NEVER_INDEX); + effectiveBucket.put(RESTRICTED_INDEX, RESTRICTED_INDEX); + for (int i = 0; i < effectiveBucket.size(); i++) { + jobStatus.setStandbyBucket(effectiveBucket.keyAt(i)); + assertEquals(effectiveBucket.valueAt(i), jobStatus.getEffectiveStandbyBucket()); + } + } + + @Test + public void testMediaBackupExemption_lateConstraint() { + final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) + .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0)) + .setOverrideDeadline(12) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .build(); + when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false); + } + + @Test + public void testMediaBackupExemption_noConnectivityConstraint() { + final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) + .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0)) + .build(); + when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false); + } + + @Test + public void testMediaBackupExemption_noContentTriggerConstraint() { + final JobInfo networkJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .build(); + when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + assertEffectiveBucketForMediaExemption(createJobStatus(networkJob), false); + } + + @Test + public void testMediaBackupExemption_wrongSourcePackage() { + final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) + .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .build(); + when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn("not.test.package"); + assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false); + } + + @Test + public void testMediaBackupExemption_nonMediaUri() { + final Uri nonMediaUri = Uri.parse("content://not-media/any/path"); + final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) + .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0)) + .addTriggerContentUri(new JobInfo.TriggerContentUri(nonMediaUri, 0)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .build(); + when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false); + } + + @Test + public void testMediaBackupExemptionGranted() { + final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT) + .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .build(); + when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE); + assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), true); + } + @Test public void testFraction() throws Exception { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 8f70ccaaa9ba..7b2b30b0280f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2087,7 +2087,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() .setFactoryResetProtectionAccounts(new ArrayList<>()) - .setFactoryResetProtectionDisabled(true) + .setFactoryResetProtectionEnabled(false) .build(); dpm.setFactoryResetProtectionPolicy(admin1, policy); @@ -2105,7 +2105,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { setupProfileOwner(); FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() - .setFactoryResetProtectionDisabled(true) + .setFactoryResetProtectionEnabled(false) .build(); assertExpectException(SecurityException.class, null, @@ -2157,7 +2157,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() .setFactoryResetProtectionAccounts(new ArrayList<>()) - .setFactoryResetProtectionDisabled(true) + .setFactoryResetProtectionEnabled(false) .build(); dpm.setFactoryResetProtectionPolicy(admin1, policy); @@ -2177,8 +2177,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy, FactoryResetProtectionPolicy actualPolicy) { - assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo( - expectedPolicy.isFactoryResetProtectionDisabled()); + assertThat(actualPolicy.isFactoryResetProtectionEnabled()).isEqualTo( + expectedPolicy.isFactoryResetProtectionEnabled()); assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(), actualPolicy.getFactoryResetProtectionAccounts()); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java index bc853c693b3a..e8818a3f4940 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java @@ -62,7 +62,7 @@ public class FactoryResetProtectionPolicyTest { FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() .setFactoryResetProtectionAccounts(accounts) - .setFactoryResetProtectionDisabled(true) + .setFactoryResetProtectionEnabled(false) .build(); testParcelAndUnparcel(policy); @@ -77,7 +77,7 @@ public class FactoryResetProtectionPolicyTest { FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder() .setFactoryResetProtectionAccounts(accounts) - .setFactoryResetProtectionDisabled(true) + .setFactoryResetProtectionEnabled(false) .build(); testParcelAndUnparcel(policy); @@ -133,8 +133,8 @@ public class FactoryResetProtectionPolicyTest { private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy, FactoryResetProtectionPolicy actualPolicy) { - assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(), - actualPolicy.isFactoryResetProtectionDisabled()); + assertEquals(expectedPolicy.isFactoryResetProtectionEnabled(), + actualPolicy.isFactoryResetProtectionEnabled()); assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(), actualPolicy.getFactoryResetProtectionAccounts()); } diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 6769faaa4c5d..0bb984ef164b 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -24,12 +24,14 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -41,6 +43,7 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Person; +import android.app.job.JobScheduler; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; @@ -49,12 +52,15 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManagerInternal; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; +import android.content.pm.parsing.AndroidPackage; import android.database.ContentObserver; import android.net.Uri; +import android.os.CancellationSignal; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; @@ -66,6 +72,7 @@ import android.telephony.TelephonyManager; import android.util.Range; import com.android.internal.app.ChooserActivity; +import com.android.internal.content.PackageMonitor; import com.android.server.LocalServices; import org.junit.After; @@ -84,6 +91,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import java.util.function.Consumer; @RunWith(JUnit4.class) public final class DataManagerTest { @@ -101,12 +109,14 @@ public final class DataManagerTest { @Mock private Context mContext; @Mock private ShortcutServiceInternal mShortcutServiceInternal; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; + @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private ShortcutManager mShortcutManager; @Mock private UserManager mUserManager; @Mock private TelephonyManager mTelephonyManager; @Mock private TelecomManager mTelecomManager; @Mock private ContentResolver mContentResolver; @Mock private ScheduledExecutorService mExecutorService; + @Mock private JobScheduler mJobScheduler; @Mock private ScheduledFuture mScheduledFuture; @Mock private StatusBarNotification mStatusBarNotification; @Mock private Notification mNotification; @@ -114,6 +124,7 @@ public final class DataManagerTest { private NotificationChannel mNotificationChannel; private DataManager mDataManager; private int mCallingUserId; + private CancellationSignal mCancellationSignal; private TestInjector mInjector; @Before @@ -124,6 +135,15 @@ public final class DataManagerTest { addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); + addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternal); + AndroidPackage androidPackage = mock(AndroidPackage.class); + when(androidPackage.getPackageName()).thenReturn(TEST_PKG_NAME); + doAnswer(ans -> { + Consumer<AndroidPackage> callback = (Consumer<AndroidPackage>) ans.getArguments()[0]; + callback.accept(androidPackage); + return null; + }).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt()); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); Context originalContext = getInstrumentation().getTargetContext(); @@ -145,6 +165,10 @@ public final class DataManagerTest { when(mTelecomManager.getDefaultDialerPackage(any(UserHandle.class))) .thenReturn(TEST_PKG_NAME); + when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler); + when(mContext.getSystemServiceName(JobScheduler.class)).thenReturn( + Context.JOB_SCHEDULER_SERVICE); + when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any( TimeUnit.class))).thenReturn(mScheduledFuture); @@ -168,6 +192,8 @@ public final class DataManagerTest { mCallingUserId = USER_ID_PRIMARY; + mCancellationSignal = new CancellationSignal(); + mInjector = new TestInjector(); mDataManager = new DataManager(mContext, mInjector); mDataManager.initialize(); @@ -177,6 +203,7 @@ public final class DataManagerTest { public void tearDown() { LocalServices.removeServiceForTest(ShortcutServiceInternal.class); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); + LocalServices.removeServiceForTest(PackageManagerInternal.class); } @Test @@ -454,6 +481,101 @@ public final class DataManagerTest { assertEquals(2, activeTimeSlots.size()); } + @Test + public void testDeleteUninstalledPackageDataOnPackageRemoved() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + + PackageMonitor packageMonitor = mDataManager.getPackageMonitorForTesting(USER_ID_PRIMARY); + Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_PRIMARY); + intent.setData(Uri.parse("package:" + TEST_PKG_NAME)); + packageMonitor.onReceive(mContext, intent); + assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + } + + @Test + public void testPruneUninstalledPackageData() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + assertNotNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + + doAnswer(ans -> null).when(mPackageManagerInternal) + .forEachInstalledPackage(any(Consumer.class), anyInt()); + mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); + assertNull(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)); + } + + @Test + public void testPruneCallEventsFromNonDialer() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + long currentTimestamp = System.currentTimeMillis(); + mInjector.mCallLogQueryHelper.mEventConsumer.accept(PHONE_NUMBER, + new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_CALL_OUTGOING)); + + List<Range<Long>> activeTimeSlots = new ArrayList<>(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.CALL_EVENT_TYPES) + .getActiveTimeSlots())); + assertEquals(1, activeTimeSlots.size()); + + mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultDialer(null); + mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); + activeTimeSlots.clear(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.CALL_EVENT_TYPES) + .getActiveTimeSlots())); + assertTrue(activeTimeSlots.isEmpty()); + } + + @Test + public void testPruneSmsEventsFromNonDefaultSmsApp() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(TEST_PKG_NAME); + + long currentTimestamp = System.currentTimeMillis(); + mInjector.mMmsQueryHelper.mEventConsumer.accept(PHONE_NUMBER, + new Event(currentTimestamp - MILLIS_PER_MINUTE, Event.TYPE_SMS_OUTGOING)); + + List<Range<Long>> activeTimeSlots = new ArrayList<>(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.SMS_EVENT_TYPES) + .getActiveTimeSlots())); + assertEquals(1, activeTimeSlots.size()); + + mDataManager.getUserDataForTesting(USER_ID_PRIMARY).setDefaultSmsApp(null); + mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal); + activeTimeSlots.clear(); + mDataManager.forAllPackages(packageData -> + activeTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.SMS_EVENT_TYPES) + .getActiveTimeSlots())); + assertTrue(activeTimeSlots.isEmpty()); + } + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { LocalServices.removeServiceForTest(clazz); LocalServices.addService(clazz, mock); diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java index 1ddc21e4ea4d..e52cdf59847c 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java @@ -81,8 +81,10 @@ public final class PackageDataTest { @Test public void testGetEventHistory() { EventStore eventStore = mPackageData.getEventStore(); - eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); - eventStore.getOrCreateLocusEventHistory(LOCUS_ID).addEvent(mE2); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId()) + .addEvent(mE2); EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); @@ -96,9 +98,10 @@ public final class PackageDataTest { mIsDefaultDialer = true; mIsDefaultSmsApp = true; EventStore eventStore = mPackageData.getEventStore(); - eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); - eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3); - eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); assertTrue(mPackageData.isDefaultDialer()); assertTrue(mPackageData.isDefaultSmsApp()); @@ -113,9 +116,10 @@ public final class PackageDataTest { @Test public void testGetEventHistoryNotDefaultDialerOrSmsApp() { EventStore eventStore = mPackageData.getEventStore(); - eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); - eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3); - eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); assertFalse(mPackageData.isDefaultDialer()); assertFalse(mPackageData.isDefaultSmsApp()); @@ -125,6 +129,61 @@ public final class PackageDataTest { assertEventEquals(mE1, events.get(0)); } + @Test + public void testDeleteConversationData() { + mIsDefaultDialer = true; + mIsDefaultSmsApp = true; + EventStore eventStore = mPackageData.getEventStore(); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId()) + .addEvent(mE2); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); + + EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(4, events.size()); + + mPackageData.deleteDataForConversation(SHORTCUT_ID); + + eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertTrue(events.isEmpty()); + } + + @Test + public void testPruneOrphanEvents() { + mIsDefaultDialer = true; + mIsDefaultSmsApp = true; + EventStore eventStore = mPackageData.getEventStore(); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SHORTCUT_BASED, SHORTCUT_ID) + .addEvent(mE1); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_LOCUS_ID_BASED, LOCUS_ID.getId()) + .addEvent(mE2); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_CALL, PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateEventHistory(EventStore.CATEGORY_SMS, PHONE_NUMBER).addEvent(mE4); + + EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(4, events.size()); + + ConversationInfo conversationInfo = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .setLocusId(null) + .setContactUri(null) + .setContactPhoneNumber(null) + .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) + .build(); + mPackageData.getConversationStore().addOrUpdate(conversationInfo); + mPackageData.pruneOrphanEvents(); + eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + // Only the shortcut based event is kept. All the other events are deleted. + assertEventEquals(mE1, events.get(0)); + } + private void assertEventEquals(Event expected, Event actual) { assertEquals(expected.getTimestamp(), actual.getTimestamp()); assertEquals(expected.getType(), actual.getType()); diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java index 01d9dc00cf47..dc4876b665f7 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java @@ -19,9 +19,9 @@ package com.android.server.people.data; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -189,7 +189,7 @@ public final class UsageStatsQueryHelperTest { private void addUsageEvents(UsageEvents.Event... events) { UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{}); when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), - anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(usageEvents); + eq(UsageEvents.SHOW_ALL_EVENT_DATA))).thenReturn(usageEvents); } private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { @@ -288,14 +288,13 @@ public final class UsageStatsQueryHelperTest { @Override @NonNull - EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) { - return mShortcutEventHistory; - } - - @Override - @NonNull - EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) { - return mLocusEventHistory; + EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) { + if (category == EventStore.CATEGORY_SHORTCUT_BASED) { + return mShortcutEventHistory; + } else if (category == EventStore.CATEGORY_LOCUS_ID_BASED) { + return mLocusEventHistory; + } + throw new UnsupportedOperationException(); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 75efdd717d02..bc33f0824214 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -51,8 +51,6 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; -import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; -import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -1144,14 +1142,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testEnqueueNotificationWithTag_WritesExpectedLog() throws Exception { + public void testEnqueueNotificationWithTag_WritesExpectedLogs() throws Exception { final String tag = "testEnqueueNotificationWithTag_WritesExpectedLog"; mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, generateNotificationRecord(null).getNotification(), 0); waitForIdle(); assertEquals(1, mNotificationRecordLogger.getCalls().size()); + NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0); - assertTrue(call.shouldLog); + assertTrue(call.shouldLogReported); assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, call.event); assertNotNull(call.r); @@ -1161,7 +1160,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(PKG, call.r.getSbn().getPackageName()); assertEquals(0, call.r.getSbn().getId()); assertEquals(tag, call.r.getSbn().getTag()); - assertNotNull(call.r.getSbn().getInstanceId()); assertEquals(0, call.getInstanceId()); // Fake instance IDs are assigned in order } @@ -1180,13 +1178,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertTrue(mNotificationRecordLogger.get(0).shouldLogReported); assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, mNotificationRecordLogger.get(0).event); assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(1).shouldLog); + assertTrue(mNotificationRecordLogger.get(1).shouldLogReported); assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED, mNotificationRecordLogger.get(1).event); @@ -1195,16 +1193,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates() throws Exception { - final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdates"; + public void testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate() throws Exception { + final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnMinorUpdate"; mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, generateNotificationRecord(null).getNotification(), 0); mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, generateNotificationRecord(null).getNotification(), 0); waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); - assertFalse(mNotificationRecordLogger.get(1).shouldLog); + assertTrue(mNotificationRecordLogger.get(0).shouldLogReported); + assertEquals( + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertFalse(mNotificationRecordLogger.get(1).shouldLogReported); + assertNull(mNotificationRecordLogger.get(1).event); + } + + @Test + public void testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate() throws Exception { + final String tag = "testEnqueueNotificationWithTag_DoesNotLogOnTitleUpdate"; + mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, + generateNotificationRecord(null).getNotification(), + 0); + final Notification notif = generateNotificationRecord(null).getNotification(); + notif.extras.putString(Notification.EXTRA_TITLE, "Changed title"); + mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notif, 0); + waitForIdle(); + assertEquals(2, mNotificationRecordLogger.getCalls().size()); + assertEquals( + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertNull(mNotificationRecordLogger.get(1).event); } @Test @@ -1224,20 +1243,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, mNotificationRecordLogger.get(0).event); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertTrue(mNotificationRecordLogger.get(0).shouldLogReported); assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason); assertEquals( NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL, mNotificationRecordLogger.get(1).event); - assertTrue(mNotificationRecordLogger.get(1).shouldLog); assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); assertEquals( NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, mNotificationRecordLogger.get(2).event); - assertTrue(mNotificationRecordLogger.get(2).shouldLog); + assertTrue(mNotificationRecordLogger.get(2).shouldLogReported); // New instance ID because notification was canceled before re-post assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId()); } @@ -2605,6 +2622,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSystemNotificationListenerCanUnsnooze() throws Exception { + final NotificationRecord nr = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testSystemNotificationListenerCanUnsnooze", + nr.getSbn().getId(), nr.getSbn().getNotification(), + nr.getSbn().getUserId()); + waitForIdle(); + NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable = + mService.new SnoozeNotificationRunnable( + nr.getKey(), 100, null); + snoozeNotificationRunnable.run(); + + ManagedServices.ManagedServiceInfo listener = mListeners.new ManagedServiceInfo( + null, new ComponentName(PKG, "test_class"), mUid, true, null, 0); + listener.isSystem = true; + when(mListeners.checkServiceTokenLocked(any())).thenReturn(listener); + + mBinderService.unsnoozeNotificationFromSystemListener(null, nr.getKey()); + waitForIdle(); + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifs.length); + assertNotNull(notifs[0].getKey());//mService.getNotificationRecord(nr.getSbn().getKey())); + } + + @Test public void testSetListenerAccessForUser() throws Exception { UserHandle user = UserHandle.of(10); ComponentName c = ComponentName.unflattenFromString("package/Component"); @@ -3396,11 +3440,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // so we only get the cancel notification. assertEquals(1, mNotificationRecordLogger.getCalls().size()); - assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason); assertEquals( NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD, mNotificationRecordLogger.get(0).event); - assertTrue(mNotificationRecordLogger.get(0).shouldLog); assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); } @@ -4326,6 +4368,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testOnNotificationVisibilityChanged_triggersVisibilityLog() { + final NotificationRecord r = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + r.setTextChanged(true); + mService.addNotification(r); + + mService.mNotificationDelegate.onNotificationVisibilityChanged(new NotificationVisibility[] + {NotificationVisibility.obtain(r.getKey(), 1, 1, true)}, + new NotificationVisibility[]{}); + + assertEquals(1, mNotificationRecordLogger.getCalls().size()); + assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_OPEN, + mNotificationRecordLogger.get(0).event); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); + + mService.mNotificationDelegate.onNotificationVisibilityChanged( + new NotificationVisibility[]{}, + new NotificationVisibility[] + {NotificationVisibility.obtain(r.getKey(), 1, 1, true)} + ); + + assertEquals(2, mNotificationRecordLogger.getCalls().size()); + assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_CLOSE, + mNotificationRecordLogger.get(1).event); + assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); + } + + @Test public void testSetNotificationsShownFromListener_triggersInterruptionUsageStat() throws RemoteException { final NotificationRecord r = generateNotificationRecord( diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java index b120dbee03c5..2a17bae57c8e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java @@ -26,24 +26,26 @@ import java.util.List; */ class NotificationRecordLoggerFake implements NotificationRecordLogger { static class CallRecord extends NotificationRecordPair { - static final int INVALID = -1; - public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID; - public boolean shouldLog; public UiEventLogger.UiEventEnum event; + + // The following fields are only relevant to maybeLogNotificationPosted() calls. + static final int INVALID = -1; + public int position = INVALID, buzzBeepBlink = INVALID; + public boolean shouldLogReported; + CallRecord(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { super(r, old); - this.position = position; this.buzzBeepBlink = buzzBeepBlink; - shouldLog = shouldLog(buzzBeepBlink); - event = NotificationReportedEvent.fromRecordPair(this); + shouldLogReported = shouldLogReported(buzzBeepBlink); + event = shouldLogReported ? NotificationReportedEvent.fromRecordPair(this) : null; } - CallRecord(NotificationRecord r, int reason, int dismissalSurface) { + + CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) { super(r, null); - this.reason = reason; - shouldLog = true; - event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface); + shouldLogReported = false; + this.event = event; } } private List<CallRecord> mCalls = new ArrayList<>(); @@ -57,14 +59,19 @@ class NotificationRecordLoggerFake implements NotificationRecordLogger { } @Override - public void logNotificationReported(NotificationRecord r, NotificationRecord old, + public void maybeLogNotificationPosted(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { mCalls.add(new CallRecord(r, old, position, buzzBeepBlink)); } @Override public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { - mCalls.add(new CallRecord(r, reason, dismissalSurface)); + mCalls.add(new CallRecord(r, + NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface))); } + @Override + public void logNotificationVisibility(NotificationRecord r, boolean visible) { + mCalls.add(new CallRecord(r, NotificationEvent.fromVisibility(visible))); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 3186d539b817..1dd0b1a6f359 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -15,15 +15,18 @@ */ package com.android.server.notification; +import static com.android.server.notification.SnoozeHelper.EXTRA_KEY; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -322,8 +325,12 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r, 1000); NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL); mSnoozeHelper.snooze(r2, 1000); + reset(mAm); mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM); verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r); + ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class); + verify(mAm).cancel(captor.capture()); + assertEquals(r.getKey(), captor.getValue().getIntent().getStringExtra(EXTRA_KEY)); } @Test @@ -332,8 +339,10 @@ public class SnoozeHelperTest extends UiServiceTestCase { mSnoozeHelper.snooze(r, 1000); NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL); mSnoozeHelper.snooze(r2, 1000); + reset(mAm); mSnoozeHelper.repost(r.getKey()); verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r); + verify(mAm).cancel(any(PendingIntent.class)); } @Test @@ -370,31 +379,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { } @Test - public void testGetSnoozedByUser() throws Exception { - NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); - NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); - NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM); - NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT); - mSnoozeHelper.snooze(r, 1000); - mSnoozeHelper.snooze(r2, 1000); - mSnoozeHelper.snooze(r3, 1000); - mSnoozeHelper.snooze(r4, 1000); - IntArray profileIds = new IntArray(); - profileIds.add(UserHandle.USER_SYSTEM); - when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); - assertEquals(3, mSnoozeHelper.getSnoozed().size()); - profileIds = new IntArray(); - profileIds.add(UserHandle.USER_CURRENT); - when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); - assertEquals(1, mSnoozeHelper.getSnoozed().size()); - } - - @Test - public void testGetSnoozedByUser_managedProfiles() throws Exception { - IntArray profileIds = new IntArray(); - profileIds.add(UserHandle.USER_CURRENT); - profileIds.add(UserHandle.USER_SYSTEM); - when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); + public void testGetSnoozedBy() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java index 9647178e6f9b..e8c0362c9f32 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java @@ -144,4 +144,33 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { anyInt() /* reason */, anyString() /* packageName */); verify(taskChangeNotifier, never()).notifyActivityDismissingDockedStack(); } + + /** + * Ensures that notify focus task changes. + */ + @Test + public void testNotifyTaskFocusChanged() { + final ActivityRecord fullScreenActivityA = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + final Task taskA = fullScreenActivityA.getTask(); + + final TaskChangeNotificationController taskChangeNotifier = + mService.getTaskChangeNotificationController(); + spyOn(taskChangeNotifier); + + mService.setResumedActivityUncheckLocked(fullScreenActivityA, "resumeA"); + verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */, + eq(true) /* focused */); + reset(taskChangeNotifier); + + final ActivityRecord fullScreenActivityB = new ActivityBuilder(mService).setCreateTask(true) + .setStack(mFullscreenStack).build(); + final Task taskB = fullScreenActivityB.getTask(); + + mService.setResumedActivityUncheckLocked(fullScreenActivityB, "resumeB"); + verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskA.mTaskId) /* taskId */, + eq(false) /* focused */); + verify(taskChangeNotifier).notifyTaskFocusChanged(eq(taskB.mTaskId) /* taskId */, + eq(true) /* focused */); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java new file mode 100644 index 000000000000..8ac1d24333be --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -0,0 +1,214 @@ +/* + * 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.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; + +import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; +import static com.android.server.wm.DisplayArea.Type.ANY; +import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import static java.util.stream.Collectors.toList; + +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl; + +import org.hamcrest.CustomTypeSafeMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; + +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.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Presubmit +public class DisplayAreaPolicyBuilderTest { + + @Rule + public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule(); + + private TestWindowManagerPolicy mPolicy = new TestWindowManagerPolicy(null, null); + + @Test + public void testBuilder() { + WindowManagerService wms = mSystemServices.getWindowManagerService(); + DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms); + DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime"); + DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks"); + + final Feature foo; + final Feature bar; + + DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() + .addFeature(foo = new Feature.Builder(mPolicy, "Foo") + .upTo(TYPE_STATUS_BAR) + .and(TYPE_NAVIGATION_BAR) + .build()) + .addFeature(bar = new Feature.Builder(mPolicy, "Bar") + .all() + .except(TYPE_STATUS_BAR) + .build()) + .build(wms, mock(DisplayContent.class), root, ime, tasks); + + policy.attachDisplayAreas(); + + assertThat(policy.getDisplayAreas(foo), is(not(empty()))); + assertThat(policy.getDisplayAreas(bar), is(not(empty()))); + + assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)), + is(decendantOfOneOf(policy.getDisplayAreas(foo)))); + assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)), + is(not(decendantOfOneOf(policy.getDisplayAreas(bar))))); + + assertThat(tasks, + is(decendantOfOneOf(policy.getDisplayAreas(foo)))); + assertThat(tasks, + is(decendantOfOneOf(policy.getDisplayAreas(bar)))); + + assertThat(ime, + is(decendantOfOneOf(policy.getDisplayAreas(foo)))); + assertThat(ime, + is(decendantOfOneOf(policy.getDisplayAreas(bar)))); + + List<DisplayArea<?>> actualOrder = collectLeafAreas(root); + Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks); + actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList()); + + Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets, + v -> v.stream().min(Integer::compareTo).get()); + Map<DisplayArea<?>, Integer> expectedByMaxLayer = mapValues(zSets, + v -> v.stream().max(Integer::compareTo).get()); + + assertThat(expectedByMinLayer, is(equalTo(expectedByMaxLayer))); + assertThat(actualOrder, is(equalTo(expectedByMaxLayer))); + } + + private <K, V, R> Map<K, R> mapValues(Map<K, V> zSets, Function<V, R> f) { + return zSets.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + e -> f.apply(e.getValue()))); + } + + private List<DisplayArea<?>> collectLeafAreas(DisplayArea<?> root) { + ArrayList<DisplayArea<?>> leafs = new ArrayList<>(); + traverseLeafAreas(root, leafs::add); + return leafs; + } + + private Map<DisplayArea<?>, Set<Integer>> calculateZSets( + DisplayAreaPolicyBuilder.Result policy, DisplayArea.Root root, + DisplayArea<WindowContainer> ime, + DisplayArea<ActivityStack> tasks) { + Map<DisplayArea<?>, Set<Integer>> zSets = new HashMap<>(); + int[] types = {TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_PRESENTATION, + TYPE_APPLICATION_OVERLAY}; + for (int type : types) { + WindowToken token = tokenOfType(type); + recordLayer(policy.findAreaForToken(token), token.getWindowLayerFromType(), zSets); + } + recordLayer(tasks, APPLICATION_LAYER, zSets); + recordLayer(ime, mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD), zSets); + return zSets; + } + + private void recordLayer(DisplayArea<?> area, int layer, + Map<DisplayArea<?>, Set<Integer>> zSets) { + zSets.computeIfAbsent(area, k -> new HashSet<>()).add(layer); + } + + private Matcher<WindowContainer> decendantOfOneOf(List<? extends WindowContainer> expected) { + return new CustomTypeSafeMatcher<WindowContainer>("descendant of one of " + expected) { + @Override + protected boolean matchesSafely(WindowContainer actual) { + for (WindowContainer expected : expected) { + WindowContainer candidate = actual; + while (candidate != null && candidate.getParent() != candidate) { + if (candidate.getParent() == expected) { + return true; + } + candidate = candidate.getParent(); + } + } + return false; + } + + @Override + protected void describeMismatchSafely(WindowContainer item, + Description description) { + description.appendText("was ").appendValue(item); + while (item != null && item.getParent() != item) { + item = item.getParent(); + description.appendText(", child of ").appendValue(item); + } + } + }; + } + + private WindowToken tokenOfType(int type) { + WindowToken m = mock(WindowToken.class); + when(m.getWindowLayerFromType()).thenReturn(mPolicy.getWindowLayerFromTypeLw(type)); + return m; + } + + private static void traverseLeafAreas(DisplayArea<?> root, Consumer<DisplayArea<?>> consumer) { + boolean leaf = true; + for (int i = 0; i < root.getChildCount(); i++) { + WindowContainer child = root.getChildAt(i); + if (child instanceof DisplayArea<?>) { + traverseLeafAreas((DisplayArea<?>) child, consumer); + leaf = false; + } + } + if (leaf) { + consumer.accept(root); + } + } + + private static class SurfacelessDisplayAreaRoot extends DisplayArea.Root { + + SurfacelessDisplayAreaRoot(WindowManagerService wms) { + super(wms); + } + + @Override + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + return new MockSurfaceControlBuilder(); + } + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java index c1a1d5ecd3c8..31206315618e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java @@ -34,13 +34,13 @@ public class DisplayAreaProviderTest { @Test public void testFromResources_emptyProvider() { Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")), - Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class)); } @Test public void testFromResources_nullProvider() { Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)), - Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + Matchers.instanceOf(DisplayAreaPolicy.DefaultProvider.class)); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index f517881d835b..8ad75053060f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -56,6 +56,7 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.StrictMode; import android.os.UserHandle; +import android.util.Log; import android.view.InputChannel; import android.view.Surface; import android.view.SurfaceControl; @@ -120,11 +121,22 @@ public class SystemServicesTestRule implements TestRule { return new Statement() { @Override public void evaluate() throws Throwable { + Throwable throwable = null; try { runWithDexmakerShareClassLoader(SystemServicesTestRule.this::setUp); base.evaluate(); + } catch (Throwable t) { + throwable = t; } finally { - tearDown(); + try { + tearDown(); + } catch (Throwable t) { + if (throwable != null) { + Log.e("SystemServicesTestRule", "Suppressed: ", throwable); + t.addSuppressed(throwable); + } + throw t; + } } } }; diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 420695dc51e4..df5b311bbab1 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -500,6 +500,19 @@ public class UsageStatsService extends SystemService implements == PackageManager.PERMISSION_GRANTED); } + /** + * Obfuscate both {@link UsageEvents.Event#NOTIFICATION_SEEN} and + * {@link UsageEvents.Event#NOTIFICATION_INTERRUPTION} events if the provided calling uid does + * not hold the {@link android.Manifest.permission.MANAGE_NOTIFICATIONS} permission. + */ + private boolean shouldObfuscateNotificationEvents(int callingPid, int callingUid) { + if (callingUid == Process.SYSTEM_UID) { + return false; + } + return !(getContext().checkPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS, + callingPid, callingUid) == PackageManager.PERMISSION_GRANTED); + } + private static void deleteRecursively(File f) { File[] files = f.listFiles(); if (files != null) { @@ -1038,9 +1051,7 @@ public class UsageStatsService extends SystemService implements /** * Called by the Binder stub. */ - UsageEvents queryEvents(int userId, long beginTime, long endTime, - boolean shouldObfuscateInstantApps, boolean shouldHideShortcutInvocationEvents, - boolean shouldHideLocusIdEvents) { + UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) { synchronized (mLock) { if (!mUserUnlockedStates.get(userId)) { Slog.w(TAG, "Failed to query events for locked user " + userId); @@ -1051,8 +1062,7 @@ public class UsageStatsService extends SystemService implements if (service == null) { return null; // user was stopped or removed } - return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps, - shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents); + return service.queryEvents(beginTime, endTime, flags); } } @@ -1475,10 +1485,15 @@ public class UsageStatsService extends SystemService implements try { final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents( userId, callingPackage, callingPid, callingUid); - boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); - return UsageStatsService.this.queryEvents(userId, beginTime, endTime, - obfuscateInstantApps, hideShortcutInvocationEvents, - shouldHideLocusIdEvents); + final boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); + final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents( + callingPid, callingUid); + int flags = UsageEvents.SHOW_ALL_EVENT_DATA; + if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS; + if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS; + if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS; + if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; + return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags); } finally { Binder.restoreCallingIdentity(token); } @@ -1525,10 +1540,15 @@ public class UsageStatsService extends SystemService implements try { final boolean hideShortcutInvocationEvents = shouldHideShortcutInvocationEvents( userId, callingPackage, callingPid, callingUid); - boolean shouldHideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); - return UsageStatsService.this.queryEvents(userId, beginTime, endTime, - obfuscateInstantApps, hideShortcutInvocationEvents, - shouldHideLocusIdEvents); + final boolean obfuscateNotificationEvents = shouldObfuscateNotificationEvents( + callingPid, callingUid); + boolean hideLocusIdEvents = shouldHideLocusIdEvents(callingPid, callingUid); + int flags = UsageEvents.SHOW_ALL_EVENT_DATA; + if (obfuscateInstantApps) flags |= UsageEvents.OBFUSCATE_INSTANT_APPS; + if (hideShortcutInvocationEvents) flags |= UsageEvents.HIDE_SHORTCUT_EVENTS; + if (hideLocusIdEvents) flags |= UsageEvents.HIDE_LOCUS_EVENTS; + if (obfuscateNotificationEvents) flags |= UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; + return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags); } finally { Binder.restoreCallingIdentity(token); } @@ -2144,12 +2164,8 @@ public class UsageStatsService extends SystemService implements } @Override - public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, - boolean obfuscateInstantApps, boolean shouldHideShortcutInvocationEvents, - boolean shouldHideLocusIdEvents) { - return UsageStatsService.this.queryEvents( - userId, beginTime, endTime, obfuscateInstantApps, - shouldHideShortcutInvocationEvents, shouldHideLocusIdEvents); + public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime, int flags) { + return UsageStatsService.this.queryEvents(userId, beginTime, endTime, flags); } @Override diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index d9317ace6e24..db26d88dbfbb 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -18,6 +18,10 @@ package com.android.server.usage; import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN; import static android.app.usage.UsageEvents.Event.DEVICE_STARTUP; +import static android.app.usage.UsageEvents.HIDE_LOCUS_EVENTS; +import static android.app.usage.UsageEvents.HIDE_SHORTCUT_EVENTS; +import static android.app.usage.UsageEvents.OBFUSCATE_INSTANT_APPS; +import static android.app.usage.UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS; import static android.app.usage.UsageStatsManager.INTERVAL_BEST; import static android.app.usage.UsageStatsManager.INTERVAL_COUNT; import static android.app.usage.UsageStatsManager.INTERVAL_DAILY; @@ -481,8 +485,7 @@ class UserUsageStatsService { return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner); } - UsageEvents queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps, - boolean hideShortcutInvocationEvents, boolean hideLocusIdEvents) { + UsageEvents queryEvents(final long beginTime, final long endTime, int flags) { if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) { return null; } @@ -500,15 +503,22 @@ class UserUsageStatsService { } Event event = stats.events.get(i); - if (hideShortcutInvocationEvents - && event.mEventType == Event.SHORTCUT_INVOCATION) { + final int eventType = event.mEventType; + if (eventType == Event.SHORTCUT_INVOCATION + && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) { continue; } - if (hideLocusIdEvents - && event.mEventType == Event.LOCUS_ID_SET) { + if (eventType == Event.LOCUS_ID_SET + && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) { continue; } - if (obfuscateInstantApps) { + if ((eventType == Event.NOTIFICATION_SEEN + || eventType == Event.NOTIFICATION_INTERRUPTION) + && (flags & OBFUSCATE_NOTIFICATION_EVENTS) + == OBFUSCATE_NOTIFICATION_EVENTS) { + event = event.getObfuscatedNotificationEvent(); + } + if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) { event = event.getObfuscatedIfInstantApp(); } if (event.mPackage != null) { diff --git a/startop/iorap/functional_tests/AndroidTest.xml b/startop/iorap/functional_tests/AndroidTest.xml index 41109b43ab82..ef56fc827420 100644 --- a/startop/iorap/functional_tests/AndroidTest.xml +++ b/startop/iorap/functional_tests/AndroidTest.xml @@ -34,6 +34,11 @@ <option name="run-command" value="rm -r /data/misc/iorapd/*" /> <option name="run-command" value="sleep 1" /> + <!-- Set system properties to enable perfetto tracing, readahead and detailed logging. --> + <option name="run-command" value="setprop iorapd.perfetto.enable true" /> + <option name="run-command" value="setprop iorapd.readahead.enable true" /> + <option name="run-command" value="setprop iorapd.log.verbose true" /> + <option name="run-command" value="start iorapd" /> <!-- give it some time to restart the service; otherwise the first unit test might fail --> @@ -45,9 +50,5 @@ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> </test> - <!-- using DeviceSetup again does not work. we simply leave the device in a semi-bad - state. there is no way to clean this up as far as I know. - --> - </configuration> diff --git a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java index bd8a45c2ca00..40023878af19 100644 --- a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java +++ b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java @@ -67,7 +67,7 @@ public class IorapWorkFlowTest { private static final String TEST_ACTIVITY_NAME = "com.android.settings.Settings"; private static final String DB_PATH = "/data/misc/iorapd/sqlite.db"; - private static final Duration TIMEOUT = Duration.ofSeconds(20L); + private static final Duration TIMEOUT = Duration.ofSeconds(300L); private static final String READAHEAD_INDICATOR = "Description = /data/misc/iorapd/com.android.settings/none/com.android.settings.Settings/compiled_traces/compiled_trace.pb"; @@ -88,7 +88,7 @@ public class IorapWorkFlowTest { mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds()); } - @Test + @Test (timeout = 300000) public void testApp() throws Exception { assertThat(mDevice, notNullValue()); @@ -247,7 +247,7 @@ public class IorapWorkFlowTest { if (supplier.getAsBoolean()) { return true; } - TimeUnit.SECONDS.sleep(totalSleepTimeSeconds); + TimeUnit.SECONDS.sleep(sleepIntervalSeconds); totalSleepTimeSeconds += sleepIntervalSeconds; if (totalSleepTimeSeconds > timeout.getSeconds()) { return false; @@ -367,7 +367,7 @@ public class IorapWorkFlowTest { * * <p> This should be run as root.</p> */ - private String executeShellCommand(String cmd) throws Exception { + private static String executeShellCommand(String cmd) throws Exception { Log.i(TAG, "Execute: " + cmd); return UiDevice.getInstance( InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 51b4a31ea8b2..c51a8520ad3b 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2377,7 +2377,7 @@ public class CarrierConfigManager { * {@link CellSignalStrengthLte#USE_RSRQ}, {@link CellSignalStrengthLte#USE_RSSNR}. * * For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1). - * If the key is invalid or not configured, a default value (RSRP | RSSNR = 1 << 0 | 1 << 2) + * If the key is invalid or not configured, a default value (RSRP = 1 << 0) * will apply. * * @hide @@ -4334,7 +4334,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false); sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000); sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, - CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR); + CellSignalStrengthLte.USE_RSRP); // Default wifi configurations. sDefaults.putAll(Wifi.getDefaults()); sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false); diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 1cd45e93a52a..2529387b19b3 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -181,7 +181,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mCqi = CellInfo.UNAVAILABLE; mTimingAdvance = CellInfo.UNAVAILABLE; mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - mParametersUseForLevel = USE_RSRP | USE_RSSNR; + mParametersUseForLevel = USE_RSRP; } /** {@inheritDoc} */ @@ -236,7 +236,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P int[] rsrpThresholds, rsrqThresholds, rssnrThresholds; boolean rsrpOnly; if (cc == null) { - mParametersUseForLevel = USE_RSRP | USE_RSSNR; + mParametersUseForLevel = USE_RSRP; rsrpThresholds = sRsrpThresholds; rsrqThresholds = sRsrqThresholds; rssnrThresholds = sRssnrThresholds; diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index c40573b25068..6fdc13e6a31b 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -492,6 +492,7 @@ public interface RILConstants { int RIL_REQUEST_ENABLE_UICC_APPLICATIONS = 208; int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209; int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210; + int RIL_REQUEST_GET_BARRING_INFO = 211; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 95b8f6700c76..da45d9a258bd 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -296,6 +296,8 @@ public class AppLaunch extends InstrumentationTestCase { AppLaunchResult launchResults = null; if (hasFailureOnFirstLaunch(launch)) { // skip if the app has failures while launched first + Log.w(TAG, "Has failures on first launch: " + launch.getApp()); + forceStopApp(launch.getApp()); continue; } AtraceLogger atraceLogger = null; diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java index 61f18e0b7191..89f642fdbb66 100644 --- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java +++ b/wifi/java/android/net/wifi/wificond/WifiNl80211Manager.java @@ -49,15 +49,16 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; /** - * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The + * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used + * to encapsulate the Wi-Fi 80211nl management interface. The * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions. * * @hide */ @SystemApi -@SystemService(Context.WIFI_COND_SERVICE) -public class WifiCondManager { - private static final String TAG = "WifiCondManager"; +@SystemService(Context.WIFI_NL80211_SERVICE) +public class WifiNl80211Manager { + private static final String TAG = "WifiNl80211Manager"; private boolean mVerboseLoggingEnabled = false; /** @@ -316,14 +317,14 @@ public class WifiCondManager { public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5; /** @hide */ - public WifiCondManager(Context context) { + public WifiNl80211Manager(Context context) { mAlarmManager = context.getSystemService(AlarmManager.class); mEventHandler = new Handler(context.getMainLooper()); } /** @hide */ @VisibleForTesting - public WifiCondManager(Context context, IWificond wificond) { + public WifiNl80211Manager(Context context, IWificond wificond) { this(context); mWificond = wificond; } @@ -485,7 +486,7 @@ public class WifiCondManager { } /** - * Enable or disable verbose logging of the WifiCondManager module. + * Enable or disable verbose logging of the WifiNl80211Manager module. * @param enable True to enable verbose logging. False to disable verbose logging. */ public void enableVerboseLogging(boolean enable) { @@ -493,7 +494,7 @@ public class WifiCondManager { } /** - * Register a death notification for the WifiCondManager which acts as a proxy for the + * Register a death notification for the WifiNl80211Manager which acts as a proxy for the * wificond daemon (i.e. the death listener will be called when and if the wificond daemon * dies). * @@ -518,7 +519,7 @@ public class WifiCondManager { // We already have a wificond handle. return true; } - IBinder binder = ServiceManager.getService(Context.WIFI_COND_SERVICE); + IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE); mWificond = IWificond.Stub.asInterface(binder); if (mWificond == null) { Log.e(TAG, "Failed to get reference to wificond"); diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java index b745a341b459..a8184068ff5a 100644 --- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/WifiNl80211ManagerTest.java @@ -71,10 +71,10 @@ import java.util.List; import java.util.Set; /** - * Unit tests for {@link android.net.wifi.WifiCondManager}. + * Unit tests for {@link android.net.wifi.wificond.WifiNl80211Manager}. */ @SmallTest -public class WifiCondManagerTest { +public class WifiNl80211ManagerTest { @Mock private IWificond mWificond; @Mock @@ -86,21 +86,21 @@ public class WifiCondManagerTest { @Mock private IApInterface mApInterface; @Mock - private WifiCondManager.SoftApCallback mSoftApListener; + private WifiNl80211Manager.SoftApCallback mSoftApListener; @Mock - private WifiCondManager.SendMgmtFrameCallback mSendMgmtFrameCallback; + private WifiNl80211Manager.SendMgmtFrameCallback mSendMgmtFrameCallback; @Mock - private WifiCondManager.ScanEventCallback mNormalScanCallback; + private WifiNl80211Manager.ScanEventCallback mNormalScanCallback; @Mock - private WifiCondManager.ScanEventCallback mPnoScanCallback; + private WifiNl80211Manager.ScanEventCallback mPnoScanCallback; @Mock - private WifiCondManager.PnoScanRequestCallback mPnoScanRequestCallback; + private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback; @Mock private Context mContext; private TestLooper mLooper; private TestAlarmManager mTestAlarmManager; private AlarmManager mAlarmManager; - private WifiCondManager mWificondControl; + private WifiNl80211Manager mWificondControl; private static final String TEST_INTERFACE_NAME = "test_wlan_if"; private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1"; private static final String TEST_INVALID_INTERFACE_NAME = "asdf"; @@ -180,7 +180,7 @@ public class WifiCondManagerTest { when(mWificond.tearDownApInterface(any())).thenReturn(true); when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl); when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME); - mWificondControl = new WifiCondManager(mContext, mWificond); + mWificondControl = new WifiNl80211Manager(mContext, mWificond); assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run, mNormalScanCallback, mPnoScanCallback)); @@ -492,7 +492,7 @@ public class WifiCondManagerTest { // getScanResults should fail. assertEquals(0, mWificondControl.getScanResults(TEST_INTERFACE_NAME, - WifiCondManager.SCAN_TYPE_SINGLE_SCAN).size()); + WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN).size()); } /** @@ -786,10 +786,10 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb1 = mock( - WifiCondManager.SendMgmtFrameCallback.class); - WifiCondManager.SendMgmtFrameCallback cb2 = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb1 = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb2 = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE, Runnable::run, cb1); @@ -800,7 +800,7 @@ public class WifiCondManagerTest { mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE, Runnable::run, cb2); - verify(cb2).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED); + verify(cb2).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED); // verify SendMgmtFrame() still was only called once i.e. not called again verify(mClientInterface, times(1)) .SendMgmtFrame(any(), any(), anyInt()); @@ -811,8 +811,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameThrowsException() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -834,7 +834,7 @@ public class WifiCondManagerTest { verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue())); sendMgmtFrameEventCaptor.getValue().OnFailure( - WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); mLooper.dispatchAll(); handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm()); @@ -848,8 +848,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameSuccess() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -882,8 +882,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameFailure() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -898,10 +898,10 @@ public class WifiCondManagerTest { Runnable::run, cb); sendMgmtFrameEventCaptor.getValue().OnFailure( - WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); mLooper.dispatchAll(); verify(cb, never()).onAck(anyInt()); - verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue())); // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not @@ -917,8 +917,8 @@ public class WifiCondManagerTest { */ @Test public void testSendMgmtFrameTimeout() throws Exception { - WifiCondManager.SendMgmtFrameCallback cb = mock( - WifiCondManager.SendMgmtFrameCallback.class); + WifiNl80211Manager.SendMgmtFrameCallback cb = mock( + WifiNl80211Manager.SendMgmtFrameCallback.class); final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor = ArgumentCaptor.forClass(ISendMgmtFrameEvent.class); @@ -935,7 +935,7 @@ public class WifiCondManagerTest { handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm()); mLooper.dispatchAll(); verify(cb, never()).onAck(anyInt()); - verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT); + verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT); // verify that even if onAck() callback is triggered after timeout, // SendMgmtFrameCallback is not triggered again @@ -1006,7 +1006,8 @@ public class WifiCondManagerTest { sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS); mLooper.dispatchAll(); verify(mSendMgmtFrameCallback, never()).onAck(anyInt()); - verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT); + verify(mSendMgmtFrameCallback).onFailure( + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT); } /** @@ -1032,9 +1033,10 @@ public class WifiCondManagerTest { handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm()); // OnFailure posts to the handler sendMgmtFrameEventCaptor.getValue().OnFailure( - WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN); + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); mLooper.dispatchAll(); - verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT); + verify(mSendMgmtFrameCallback).onFailure( + WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT); } /** |