diff options
417 files changed, 9714 insertions, 4699 deletions
diff --git a/Android.bp b/Android.bp index efe530798041..8c8563139083 100644 --- a/Android.bp +++ b/Android.bp @@ -416,6 +416,13 @@ filegroup { } filegroup { + name: "graphicsstats_proto", + srcs: [ + "libs/hwui/protos/graphicsstats.proto", + ], +} + +filegroup { name: "libvibrator_aidl", srcs: [ "core/java/android/os/IExternalVibrationController.aidl", @@ -442,8 +449,6 @@ java_library { "libcore-platform-compat-config", "services-platform-compat-config", "media-provider-platform-compat-config", - "services-devicepolicy-platform-compat-config", - "services-core-platform-compat-config", ], static_libs: [ // If MimeMap ever becomes its own APEX, then this dependency would need to be removed @@ -489,7 +494,8 @@ java_library { // TODO(b/140299412): should be framework-wifi-stubs "framework-wifi", "ike-stubs", - // TODO(jiyong): add more stubs for APEXes here + // TODO(b/147200698): should be the stub of framework-tethering + "framework-tethering", ], sdk_version: "core_platform", apex_available: ["//apex_available:platform"], @@ -619,6 +625,15 @@ java_library { } filegroup { + name: "framework-ike-shared-srcs", + visibility: ["//frameworks/opt/net/ike"], + srcs: [ + "core/java/android/net/annotations/PolicyDirection.java", + "telephony/java/android/telephony/Annotation.java", + ], +} + +filegroup { name: "framework-networkstack-shared-srcs", srcs: [ // TODO: remove these annotations as soon as we can use andoid.support.annotations.* diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp index 3dc5a2c10b6b..1f30dda21ef7 100644 --- a/apex/appsearch/framework/Android.bp +++ b/apex/appsearch/framework/Android.bp @@ -26,9 +26,16 @@ java_library { installable: true, sdk_version: "core_platform", // TODO(b/146218515) should be core_current srcs: [":framework-appsearch-sources"], + hostdex: true, // for hiddenapi check libs: [ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs ], + visibility: [ + "//frameworks/base/apex/appsearch:__subpackages__", + // TODO(b/146218515) remove this when framework is built with the stub of appsearch + "//frameworks/base", + ], + apex_available: ["com.android.appsearch"], } metalava_appsearch_docs_args = diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 041825c235d0..6109b713de24 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -1,5 +1,6 @@ package com.android.server.usage; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageEvents; @@ -99,8 +100,18 @@ public interface AppStandbyInternal { List<AppStandbyInfo> getAppStandbyBuckets(int userId); - void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, - int reason, long elapsedRealtime, boolean resetTimeout); + /** + * Changes an app's standby bucket to the provided value. The caller can only set the standby + * bucket for a different app than itself. + */ + void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, + int callingPid); + + /** + * Changes the app standby bucket for multiple apps at once. + */ + void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid, + int callingPid); void addActiveDeviceAdmin(String adminPkg, int userId); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 2f8b5130edb0..58eb58961ac4 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -47,6 +47,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppGlobals; @@ -101,7 +102,6 @@ import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.usage.AppIdleHistory.AppUsageHistory; -import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import java.io.File; import java.io.PrintWriter; @@ -109,6 +109,7 @@ import java.time.Duration; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -1014,14 +1015,57 @@ public class AppStandbyController implements AppStandbyInternal { } } + @Override + public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, + int callingUid, int callingPid) { + setAppStandbyBuckets( + Collections.singletonList(new AppStandbyInfo(packageName, bucket)), + userId, callingUid, callingPid); + } + + @Override + public void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, + int callingUid, int callingPid) { + userId = ActivityManager.handleIncomingUser( + callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null); + final boolean shellCaller = callingUid == Process.ROOT_UID + || callingUid == Process.SHELL_UID; + final boolean systemCaller = UserHandle.isCore(callingUid); + final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED; + final int packageFlags = PackageManager.MATCH_ANY_USER + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE; + final int numApps = appBuckets.size(); + final long elapsedRealtime = mInjector.elapsedRealtime(); + for (int i = 0; i < numApps; ++i) { + final AppStandbyInfo bucketInfo = appBuckets.get(i); + final String packageName = bucketInfo.mPackageName; + final int bucket = bucketInfo.mStandbyBucket; + if (bucket < STANDBY_BUCKET_ACTIVE || bucket > STANDBY_BUCKET_NEVER) { + throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket); + } + final int packageUid = mInjector.getPackageManagerInternal() + .getPackageUid(packageName, packageFlags, userId); + // Caller cannot set their own standby state + if (packageUid == callingUid) { + throw new IllegalArgumentException("Cannot set your own standby bucket"); + } + if (packageUid < 0) { + throw new IllegalArgumentException( + "Cannot set standby bucket for non existent package (" + packageName + ")"); + } + setAppStandbyBucket(packageName, userId, bucket, reason, elapsedRealtime, shellCaller); + } + } + @VisibleForTesting void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, - int reason, long elapsedRealtime) { - setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false); + int reason) { + setAppStandbyBucket( + packageName, userId, newBucket, reason, mInjector.elapsedRealtime(), false); } - @Override - public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, + private void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, int reason, long elapsedRealtime, boolean resetTimeout) { synchronized (mAppIdleLock) { // If the package is not installed, don't allow the bucket to be set. @@ -1444,6 +1488,10 @@ public class AppStandbyController implements AppStandbyInternal { mBatteryStats.noteEvent(event, packageName, uid); } + PackageManagerInternal getPackageManagerInternal() { + return mPackageManagerInternal; + } + boolean isPackageEphemeral(int userId, String packageName) { return mPackageManagerInternal.isPackageEphemeral(userId, packageName); } diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 6bd0086681a7..18382a488428 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -55,6 +55,13 @@ java_library { jarjar_rules: "jarjar_rules.txt", plugins: ["java_api_finder"], + + hostdex: true, // for hiddenapi check + visibility: ["//frameworks/av/apex:__subpackages__"], + apex_available: [ + "com.android.media", + "test_com.android.media", + ], } filegroup { diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING index 7e7762337afc..4e1883382e2c 100644 --- a/apex/sdkextensions/TEST_MAPPING +++ b/apex/sdkextensions/TEST_MAPPING @@ -1,7 +1,7 @@ { "presubmit": [ { - "name": "CtsSdkExtTestCases" + "name": "CtsSdkExtensionsTestCases" }, { "name": "apiextensions_e2e_tests" diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp index 5504f4e5dd8e..dd174734df6d 100644 --- a/apex/sdkextensions/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -36,6 +36,11 @@ java_library { "//frameworks/base/apex/sdkextensions", "//frameworks/base/apex/sdkextensions/testing", ], + hostdex: true, // for hiddenapi check + apex_available: [ + "com.android.sdkext", + "test_com.android.sdkext", + ], } droidstubs { diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl index 21b7767e932d..99b9d398e30c 100644 --- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl +++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl @@ -67,11 +67,4 @@ interface IStatsCompanionService { /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); - - /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */ - oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, - in int[] additiveFields, IPullAtomCallback pullerCallback); - - /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */ - oneway void unregisterPullAtomCallback(int atomTag); } diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl index dec56345ec2f..4a259f50d2f6 100644 --- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl +++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl @@ -17,6 +17,7 @@ package android.os; import android.app.PendingIntent; +import android.os.IPullAtomCallback; /** * Binder interface to communicate with the Java-based statistics service helper. @@ -125,4 +126,11 @@ interface IStatsManagerService { * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. */ void removeConfiguration(in long configId, in String packageName); -}
\ No newline at end of file + + /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */ + oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + in int[] additiveFields, IPullAtomCallback pullerCallback); + + /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */ + oneway void unregisterPullAtomCallback(int atomTag); +} diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index a2b0577fe001..0b46645ad06f 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -37,7 +37,16 @@ java_library { // TODO(b/146230220): Use framework-system-stubs instead. "android_system_stubs_current", ], - // TODO:(b/146210774): Add apex_available field. + hostdex: true, // for hiddenapi check + visibility: [ + "//frameworks/base/apex/statsd:__subpackages__", + //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs + "//frameworks/base", + ], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], } droidstubs { diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index d57afeeb7157..a9081786866c 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -75,7 +75,6 @@ import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.IPullAtomCallback; import android.os.IStatsCompanionService; import android.os.IStatsd; import android.os.IStoraged; @@ -263,71 +262,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private StatsManagerService mStatsManagerService; - private static final class PullerKey { - private final int mUid; - private final int mAtomTag; - - PullerKey(int uid, int atom) { - mUid = uid; - mAtomTag = atom; - } - - public int getUid() { - return mUid; - } - - public int getAtom() { - return mAtomTag; - } - - @Override - public int hashCode() { - return Objects.hash(mUid, mAtomTag); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof PullerKey) { - PullerKey other = (PullerKey) obj; - return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); - } - return false; - } - } - - private static final class PullerValue { - private final long mCoolDownNs; - private final long mTimeoutNs; - private int[] mAdditiveFields; - private IPullAtomCallback mCallback; - - PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields, - IPullAtomCallback callback) { - mCoolDownNs = coolDownNs; - mTimeoutNs = timeoutNs; - mAdditiveFields = additiveFields; - mCallback = callback; - } - - public long getCoolDownNs() { - return mCoolDownNs; - } - - public long getTimeoutNs() { - return mTimeoutNs; - } - - public int[] getAdditiveFields() { - return mAdditiveFields; - } - - public IPullAtomCallback getCallback() { - return mCallback; - } - } - - private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>(); - private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); private WifiManager mWifiManager = null; @@ -2634,57 +2568,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - @Override - public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, - int[] additiveFields, IPullAtomCallback pullerCallback) { - synchronized (sStatsdLock) { - // Always cache the puller in SCS. - // If statsd is down, we will register it when it comes back up. - int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - PullerKey key = new PullerKey(callingUid, atomTag); - PullerValue val = new PullerValue( - coolDownNs, timeoutNs, additiveFields, pullerCallback); - mPullers.put(key, val); - - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag); - return; - } - try { - sStatsd.registerPullAtomCallback( - callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - - @Override - public void unregisterPullAtomCallback(int atomTag) { - synchronized (sStatsdLock) { - // Always remove the puller in SCS. - // If statsd is down, we will not register it when it comes back up. - int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - PullerKey key = new PullerKey(callingUid, atomTag); - mPullers.remove(key); - - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag); - return; - } - try { - sStatsd.unregisterPullAtomCallback(callingUid, atomTag); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } // Statsd related code @@ -2763,8 +2646,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Pull the latest state of UID->app name, version mapping when // statsd starts. informAllUidsLocked(mContext); - // Register all pullers. If SCS has just started, this should be empty. - registerAllPullersLocked(); } finally { restoreCallingIdentity(token); } @@ -2776,17 +2657,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - @GuardedBy("sStatsdLock") - private void registerAllPullersLocked() throws RemoteException { - // TODO: pass in one call, using a file descriptor (similar to uidmap). - for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) { - PullerKey key = entry.getKey(); - PullerValue val = entry.getValue(); - sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(), - val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback()); - } - } - private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index b27d0f7699fc..04d8b006f51d 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -24,6 +24,7 @@ import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; import android.os.Binder; +import android.os.IPullAtomCallback; import android.os.IStatsManagerService; import android.os.IStatsd; import android.os.Process; @@ -60,8 +61,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { @GuardedBy("mLock") private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>(); @GuardedBy("mLock") - private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = - new ArrayMap<>(); + private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>(); @GuardedBy("mLock") private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap = new ArrayMap<>(); @@ -72,8 +72,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { } private static class ConfigKey { - private int mUid; - private long mConfigId; + private final int mUid; + private final long mConfigId; ConfigKey(int uid, long configId) { mUid = uid; @@ -103,6 +103,126 @@ public class StatsManagerService extends IStatsManagerService.Stub { } } + private static class PullerKey { + private final int mUid; + private final int mAtomTag; + + PullerKey(int uid, int atom) { + mUid = uid; + mAtomTag = atom; + } + + public int getUid() { + return mUid; + } + + public int getAtom() { + return mAtomTag; + } + + @Override + public int hashCode() { + return Objects.hash(mUid, mAtomTag); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PullerKey) { + PullerKey other = (PullerKey) obj; + return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); + } + return false; + } + } + + private static class PullerValue { + private final long mCoolDownNs; + private final long mTimeoutNs; + private final int[] mAdditiveFields; + private final IPullAtomCallback mCallback; + + PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields, + IPullAtomCallback callback) { + mCoolDownNs = coolDownNs; + mTimeoutNs = timeoutNs; + mAdditiveFields = additiveFields; + mCallback = callback; + } + + public long getCoolDownNs() { + return mCoolDownNs; + } + + public long getTimeoutNs() { + return mTimeoutNs; + } + + public int[] getAdditiveFields() { + return mAdditiveFields; + } + + public IPullAtomCallback getCallback() { + return mCallback; + } + } + + private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>(); + + @Override + public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + int[] additiveFields, IPullAtomCallback pullerCallback) { + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + PullerKey key = new PullerKey(callingUid, atomTag); + PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback); + + // Always cache the puller in StatsManagerService. If statsd is down, we will register the + // puller when statsd comes back up. + synchronized (mLock) { + mPullers.put(key, val); + } + + IStatsd statsd = getStatsdNonblocking(); + if (statsd == null) { + return; + } + + try { + statsd.registerPullAtomCallback( + callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void unregisterPullAtomCallback(int atomTag) { + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + PullerKey key = new PullerKey(callingUid, atomTag); + + // Always remove the puller from StatsManagerService even if statsd is down. When statsd + // comes back up, we will not re-register the removed puller. + synchronized (mLock) { + mPullers.remove(key); + } + + IStatsd statsd = getStatsdNonblocking(); + if (statsd == null) { + return; + } + + try { + statsd.unregisterPullAtomCallback(callingUid, atomTag); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public void setDataFetchOperation(long configId, PendingIntent pendingIntent, String packageName) { @@ -441,46 +561,85 @@ public class StatsManagerService extends IStatsManagerService.Stub { if (statsd == null) { return; } - // Since we do not want to make an IPC with the a lock held, we first create local deep - // copies of the data with the lock held before iterating through the maps. + + final long token = Binder.clearCallingIdentity(); + try { + registerAllPullers(statsd); + registerAllDataFetchOperations(statsd); + registerAllActiveConfigsChangedOperations(statsd); + registerAllBroadcastSubscribers(statsd); + } catch (RemoteException e) { + Slog.e(TAG, "StatsManager failed to (re-)register data with statsd"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllPullers(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. + ArrayMap<PullerKey, PullerValue> pullersCopy; + synchronized (mLock) { + pullersCopy = new ArrayMap<>(mPullers); + } + + for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) { + PullerKey key = entry.getKey(); + PullerValue value = entry.getValue(); + statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(), + value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback()); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy; - ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; - ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy; synchronized (mLock) { dataFetchCopy = new ArrayMap<>(mDataFetchPirMap); - activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); - broadcastSubscriberCopy = new ArrayMap<>(); - for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry - : mBroadcastSubscriberPirMap.entrySet()) { - broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue())); - } } + for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) { ConfigKey key = entry.getKey(); - try { - statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setDataFetchOperation from pirMap"); - } + statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. + ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; + synchronized (mLock) { + activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); + } + + for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) { + statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); } - for (Map.Entry<Integer, PendingIntentRef> entry - : activeConfigsChangedCopy.entrySet()) { - try { - statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap"); + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a deep copy of + // the data with the lock held before iterating through the map. + ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy = + new ArrayMap<>(); + synchronized (mLock) { + for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : + mBroadcastSubscriberPirMap.entrySet()) { + broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue())); } } - for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry - : broadcastSubscriberCopy.entrySet()) { + + for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : + mBroadcastSubscriberPirMap.entrySet()) { + ConfigKey configKey = entry.getKey(); for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) { - ConfigKey configKey = entry.getKey(); - try { - statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), - subscriberEntry.getValue(), configKey.getUid()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap"); - } + statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), + subscriberEntry.getValue(), configKey.getUid()); } } } diff --git a/api/current.txt b/api/current.txt index b673df0b5c90..3e6a055271c9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5837,6 +5837,7 @@ package android.app { method public void enableLights(boolean); method public void enableVibration(boolean); method public android.media.AudioAttributes getAudioAttributes(); + method @Nullable public String getConversationId(); method public String getDescription(); method public String getGroup(); method public String getId(); @@ -5844,12 +5845,14 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public CharSequence getName(); + method @Nullable public String getParentChannelId(); method public android.net.Uri getSound(); method public long[] getVibrationPattern(); method public boolean hasUserSetImportance(); method public boolean hasUserSetSound(); method public void setAllowBubbles(boolean); method public void setBypassDnd(boolean); + method public void setConversationId(@Nullable String, @Nullable String); method public void setDescription(String); method public void setGroup(String); method public void setImportance(int); @@ -5862,6 +5865,7 @@ package android.app { method public boolean shouldShowLights(); method public boolean shouldVibrate(); method public void writeToParcel(android.os.Parcel, int); + field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s"; field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR; field public static final String DEFAULT_CHANNEL_ID = "miscellaneous"; } @@ -5903,6 +5907,7 @@ package android.app { method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(String); + method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String); method public android.app.NotificationChannelGroup getNotificationChannelGroup(String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); @@ -9823,6 +9828,7 @@ package android.content { method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int); method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); + method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle); method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String); method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int); method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String); @@ -11356,6 +11362,9 @@ package android.content.pm { } public class CrossProfileApps { + method public boolean canInteractAcrossProfiles(); + method public boolean canRequestInteractAcrossProfiles(); + method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent(); method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle); method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle); method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles(); @@ -11928,6 +11937,7 @@ package android.content.pm { field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore"; field public static final String FEATURE_TELEPHONY = "android.hardware.telephony"; field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; + field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; @@ -29155,6 +29165,7 @@ package android.net { ctor public DhcpInfo(); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR; field public int dns1; field public int dns2; field public int gateway; @@ -35827,6 +35838,7 @@ package android.os { field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8 field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6 + field public static final int WIFI_UID = 1010; // 0x3f2 } public abstract class ProxyFileDescriptorCallback { @@ -36061,6 +36073,7 @@ package android.os { method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); method public boolean hasUserRestriction(String); method public boolean isDemoUser(); + method public boolean isManagedProfile(); method public boolean isQuietModeEnabled(android.os.UserHandle); method public boolean isSystemUser(); method public boolean isUserAGoat(); @@ -39524,7 +39537,9 @@ package android.provider { field public static final String ACTION_LOCALE_SETTINGS = "android.settings.LOCALE_SETTINGS"; field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS"; field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; + field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"; field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; + field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION"; field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; @@ -42668,9 +42683,13 @@ package android.service.voice { method public android.content.Intent createEnrollIntent(); method public android.content.Intent createReEnrollIntent(); method public android.content.Intent createUnEnrollIntent(); + method public int getParameter(int); method public int getSupportedRecognitionModes(); + method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); + method public int setParameter(int, int); method public boolean startRecognition(int); method public boolean stopRecognition(); + field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 @@ -42695,6 +42714,11 @@ package android.service.voice { method @Nullable public byte[] getTriggerAudio(); } + public static final class AlwaysOnHotwordDetector.ModelParamRange { + method public int end(); + method public int start(); + } + public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); @@ -45010,6 +45034,7 @@ package android.telephony { field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff + field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array"; @@ -45207,6 +45232,7 @@ package android.telephony { public static final class CarrierConfigManager.Ims { field public static final String KEY_PREFIX = "ims."; + field public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = "ims.wifi_off_deferring_time_int"; } public abstract class CellIdentity implements android.os.Parcelable { @@ -46134,12 +46160,12 @@ package android.telephony { method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle); method public boolean hasCarrierPrivileges(); method public boolean hasIccCard(); - method public boolean iccCloseLogicalChannel(int); - method public byte[] iccExchangeSimIO(int, int, int, int, int, String); + method @Deprecated public boolean iccCloseLogicalChannel(int); + method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String); method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String); - method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int); - method public String iccTransmitApduBasicChannel(int, int, int, int, int, String); - method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String); + method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int); + method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String); + method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String); method public boolean isConcurrentVoiceAndDataSupported(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled(); @@ -46157,7 +46183,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); method public void sendDialerSpecialCode(String); - method public String sendEnvelopeWithStatus(String); + method @Deprecated public String sendEnvelopeWithStatus(String); method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean); @@ -51599,9 +51625,10 @@ package android.view { method public boolean dispatchUnhandledMove(android.view.View, int); method protected void dispatchVisibilityChanged(@NonNull android.view.View, int); method public void dispatchWindowFocusChanged(boolean); - method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets); - method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); method public void dispatchWindowSystemUiVisiblityChanged(int); method public void dispatchWindowVisibilityChanged(int); method @CallSuper public void draw(android.graphics.Canvas); @@ -53281,9 +53308,10 @@ package android.view { } public interface WindowInsetsAnimationCallback { - method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets); - method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); } public static final class WindowInsetsAnimationCallback.AnimationBounds { diff --git a/api/system-current.txt b/api/system-current.txt index 588686a38489..803b4f04d2cf 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -152,6 +152,7 @@ package android { field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT"; field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES"; + field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION"; field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS"; @@ -1535,6 +1536,10 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); } + public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + } + public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile { method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); @@ -1543,12 +1548,20 @@ package android.bluetooth { field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; } + public final class BluetoothMap implements android.bluetooth.BluetoothProfile { + method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); + field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; + } + public final class BluetoothPan implements android.bluetooth.BluetoothProfile { method protected void finalize(); method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); method public boolean isTetheringOn(); method public void setBluetoothTethering(boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; field public static final int LOCAL_NAP_ROLE = 1; // 0x1 @@ -1671,7 +1684,6 @@ package android.content { } public abstract class Context { - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int); method public abstract android.content.Context createCredentialProtectedStorageContext(); method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; @@ -1780,6 +1792,7 @@ package android.content { field @Deprecated public static final String EXTRA_SIM_STATE = "ss"; field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; + field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000 field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED"; field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK"; @@ -3545,7 +3558,6 @@ package android.hardware.soundtrigger { } public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { - method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR; field public final int end; @@ -3566,6 +3578,7 @@ package android.hardware.soundtrigger { field public final int powerConsumptionMw; field public final int recognitionModes; field public final boolean returnsTriggerInEvent; + field @NonNull public final String supportedModelArch; field public final boolean supportsCaptureTransition; field public final boolean supportsConcurrentCapture; field @NonNull public final java.util.UUID uuid; @@ -4311,7 +4324,7 @@ package android.media.session { } public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener { - method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token); + method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token); } public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener { @@ -4367,9 +4380,9 @@ package android.media.soundtrigger { method public int getDetectionServiceOperationsTimeout(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties(); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model); } @@ -4855,6 +4868,7 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { + method public boolean deduceRestrictedCapability(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String); @@ -5835,8 +5849,8 @@ package android.net.wifi { method public int getMaxNumberOfClients(); method @Nullable public String getPassphrase(); method public int getSecurityType(); + method public int getShutdownTimeoutMillis(); method @Nullable public String getSsid(); - method @Nullable public String getWpa2Passphrase(); method public boolean isHiddenSsid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int BAND_2GHZ = 1; // 0x1 @@ -5860,8 +5874,8 @@ package android.net.wifi { method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String); - method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String); } public final class SoftApInfo implements android.os.Parcelable { @@ -6024,6 +6038,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>); method public boolean isApMacRandomizationSupported(); method public boolean isConnectedMacRandomizationSupported(); method @Deprecated public boolean isDeviceToDeviceRttSupported(); @@ -7185,7 +7200,7 @@ package android.os { ctor public UpdateEngine(); method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]); method public void applyPayload(String, long, long, String[]); - method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]); + method public void applyPayload(@NonNull android.content.res.AssetFileDescriptor, @NonNull String[]); method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler); method public boolean bind(android.os.UpdateEngineCallback); method public void cancel(); @@ -7270,7 +7285,6 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(); @@ -7860,6 +7874,7 @@ package android.provider { } public final class Settings { + method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean); field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS"; field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS"; field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; @@ -8658,7 +8673,10 @@ package android.service.notification { method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel); method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationVisibilityChanged(@NonNull String, boolean); method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>); + method public void onPanelHidden(); + method public void onPanelRevealed(int); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; @@ -10399,10 +10417,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int); - method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String); - method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int); diff --git a/api/test-current.txt b/api/test-current.txt index d017dd63bdd3..00a2c29cd06f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -435,6 +435,8 @@ package android.app { } public class StatusBarManager { + method public void collapsePanels(); + method public void expandNotificationsPanel(); method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo(); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean); @@ -2929,7 +2931,10 @@ package android.service.notification { method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel); method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationVisibilityChanged(@NonNull String, boolean); method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>); + method public void onPanelHidden(); + method public void onPanelRevealed(int); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 118a508a9071..1c6867c39790 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -78,7 +78,6 @@ cc_defaults { "src/external/StatsPuller.cpp", "src/external/StatsPullerManager.cpp", "src/external/SubsystemSleepStatePuller.cpp", - "src/external/SurfaceflingerStatsPuller.cpp", "src/external/TrainInfoPuller.cpp", "src/FieldValue.cpp", "src/guardrail/StatsdStats.cpp", @@ -148,7 +147,6 @@ cc_defaults { "libincident", "libservices", "libstatsmetadata", - "libtimestats_proto", "libutils", ], } @@ -297,7 +295,6 @@ cc_test { "tests/external/puller_util_test.cpp", "tests/external/StatsCallbackPuller_test.cpp", "tests/external/StatsPuller_test.cpp", - "tests/external/SurfaceflingerStatsPuller_test.cpp", "tests/FieldValue_test.cpp", "tests/guardrail/StatsdStats_test.cpp", "tests/indexed_priority_queue_test.cpp", diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 19b9709e1d41..2bacfbc57395 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -333,10 +333,11 @@ message Atom { MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"]; MediaProviderIdleMaintenance media_provider_idle_maintenance = 237 [(module) = "mediaprovider"]; + RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238; } // Pulled events will start at field 10000. - // Next: 10068 + // Next: 10069 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -405,6 +406,7 @@ message Atom { VmsClientStats vms_client_stats = 10065; NotificationRemoteViews notification_remote_views = 10066; DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067; + GraphicsStats graphics_stats = 10068; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -7338,6 +7340,17 @@ message UpdateEngineSuccessfulUpdateReported { } /** + * Reported when the RebootEscrow HAL has attempted to recover the escrowed + * key to indicate whether it was successful or not. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java + */ +message RebootEscrowRecoveryReported { + optional bool successful = 1; +} + +/** * Global display pipeline metrics reported by SurfaceFlinger. * Pulled from: * frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp @@ -7553,3 +7566,69 @@ message DangerousPermissionStateSampled { optional int32 permission_flags = 4; } +/** + * HWUI renders pipeline type: GL (0) or Vulkan (1). + */ +enum PipelineType { + GL = 0; + VULKAN = 1; +} + +/** + * HWUI stats for a given app. + */ +message GraphicsStats { + // The package name of the app + optional string package_name = 1; + + // The version code of the app + optional int64 version_code = 2; + + // The start & end timestamps in UTC as + // milliseconds since January 1, 1970 + // Compatible with java.util.Date#setTime() + optional int64 stats_start = 3; + + optional int64 stats_end = 4; + + // HWUI renders pipeline type: GL or Vulkan. + optional PipelineType pipeline = 5; + + // Distinct frame count. + optional int32 total_frames = 6; + + // Number of "missed vsync" events. + optional int32 missed_vsync_count = 7; + + // Number of frames in triple-buffering scenario (high input latency) + optional int32 high_input_latency_count = 8; + + // Number of "slow UI thread" events. + optional int32 slow_ui_thread_count = 9; + + // Number of "slow bitmap upload" events. + optional int32 slow_bitmap_upload_count = 10; + + // Number of "slow draw" events. + optional int32 slow_draw_count = 11; + + // Number of frames that missed their deadline (aka, visibly janked) + optional int32 missed_deadline_count = 12; + + // The frame time histogram for the package + optional FrameTimingHistogram cpu_histogram = 13 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // The gpu frame time histogram for the package + optional FrameTimingHistogram gpu_histogram = 14 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // UI mainline module version. + optional int64 version_ui_module = 15; + + // If true, these are HWUI stats for up to a 24h period for a given app from today. + // If false, these are HWUI stats for a 24h period for a given app from the last complete + // day (yesterday). Stats from yesterday stay constant, while stats from today may change as + // more apps are running / rendering. + optional bool is_today = 16; +} diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 50896f84da43..1d31873209b2 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -41,7 +41,6 @@ #include "StatsCallbackPullerDeprecated.h" #include "StatsCompanionServicePuller.h" #include "SubsystemSleepStatePuller.h" -#include "SurfaceflingerStatsPuller.h" #include "TrainInfoPuller.h" #include "statslog.h" @@ -273,10 +272,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { // App ops {{.atomTag = android::util::APP_OPS}, {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}}, - // SurfaceflingerStatsGlobalInfo - {{.atomTag = android::util::SURFACEFLINGER_STATS_GLOBAL_INFO}, - {.puller = - new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}}, // VmsClientStats {{.atomTag = android::util::VMS_CLIENT_STATS}, {.additiveFields = {5, 6, 7, 8, 9, 10}, diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp deleted file mode 100644 index 23b2236f35f2..000000000000 --- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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. - */ - -#include "SurfaceflingerStatsPuller.h" - -#include <cutils/compiler.h> - -#include <numeric> - -#include "logd/LogEvent.h" -#include "stats_log_util.h" -#include "statslog.h" - -namespace android { -namespace os { -namespace statsd { - -SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) { -} - -bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) { - switch (mTagId) { - case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO: - return pullGlobalInfo(data); - default: - break; - } - - return false; -} - -static int64_t getTotalTime( - const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>& - buckets) { - int64_t total = 0; - for (const auto& bucket : buckets) { - if (bucket.time_millis() == 1000) { - continue; - } - - total += bucket.time_millis() * bucket.frame_count(); - } - - return total; -} - -bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) { - std::string protoBytes; - if (CC_UNLIKELY(mStatsProvider)) { - protoBytes = mStatsProvider(); - } else { - std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose); - if (!pipe.get()) { - return false; - } - char buf[1024]; - size_t bytesRead = 0; - do { - bytesRead = fread(buf, 1, sizeof(buf), pipe.get()); - protoBytes.append(buf, bytesRead); - } while (bytesRead > 0); - } - surfaceflinger::SFTimeStatsGlobalProto proto; - proto.ParseFromString(protoBytes); - - int64_t totalTime = getTotalTime(proto.present_to_present()); - - data->clear(); - data->reserve(1); - std::shared_ptr<LogEvent> event = - make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(), - getElapsedRealtimeNs()); - if (!event->write(proto.total_frames())) return false; - if (!event->write(proto.missed_frames())) return false; - if (!event->write(proto.client_composition_frames())) return false; - if (!event->write(proto.display_on_time())) return false; - if (!event->write(totalTime)) return false; - event->init(); - data->emplace_back(event); - - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h deleted file mode 100644 index ed7153edf797..000000000000 --- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include <timestatsproto/TimeStatsProtoHeader.h> - -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Pull metrics from Surfaceflinger - */ -class SurfaceflingerStatsPuller : public StatsPuller { -public: - explicit SurfaceflingerStatsPuller(const int tagId); - - // StatsPuller interface - bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override; - -protected: - // Test-only, for injecting fake data - using StatsProvider = std::function<std::string()>; - StatsProvider mStatsProvider; - -private: - bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp deleted file mode 100644 index 5b7a30d4a5aa..000000000000 --- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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. - */ - -#undef LOG_TAG -#define LOG_TAG "SurfaceflingerStatsPuller_test" - -#include "src/external/SurfaceflingerStatsPuller.h" -#include "statslog.h" - -#include <gtest/gtest.h> -#include <log/log.h> - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller { -public: - TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){}; - - void injectStats(const StatsProvider& statsProvider) { - mStatsProvider = statsProvider; - } -}; - -class SurfaceflingerStatsPullerTest : public ::testing::Test { -public: - SurfaceflingerStatsPullerTest() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - } - - ~SurfaceflingerStatsPullerTest() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); - } -}; - -TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) { - surfaceflinger::SFTimeStatsGlobalProto proto; - proto.set_total_frames(1); - proto.set_missed_frames(2); - proto.set_client_composition_frames(2); - proto.set_display_on_time(4); - - auto bucketOne = proto.add_present_to_present(); - bucketOne->set_time_millis(2); - bucketOne->set_frame_count(4); - auto bucketTwo = proto.add_present_to_present(); - bucketTwo->set_time_millis(4); - bucketTwo->set_frame_count(1); - auto bucketThree = proto.add_present_to_present(); - bucketThree->set_time_millis(1000); - bucketThree->set_frame_count(1); - static constexpr int64_t expectedAnimationMillis = 12; - TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO); - - puller.injectStats([&] { - return proto.SerializeAsString(); - }); - puller.ForceClearCache(); - vector<std::shared_ptr<LogEvent>> outData; - puller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId()); - EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value); - EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value); - EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value); - EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value); - EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt index 4d0acb3c2330..4aaf72728cbf 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -22519,8 +22519,6 @@ HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhone(Landroid/cont HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState; -HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V -HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object; HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo; HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String; @@ -37729,7 +37727,6 @@ Lcom/android/internal/telephony/PhoneFactory; Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState; Lcom/android/internal/telephony/PhoneInternalInterface; Lcom/android/internal/telephony/PhoneNotifier; -Lcom/android/internal/telephony/PhoneStateIntentReceiver; Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper; Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper; Lcom/android/internal/telephony/PhoneSubInfoController; diff --git a/config/preloaded-classes b/config/preloaded-classes index 97f009c09217..e53c74bdfc4b 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -4841,7 +4841,6 @@ com.android.internal.telephony.PhoneConstants$State com.android.internal.telephony.PhoneFactory com.android.internal.telephony.PhoneInternalInterface com.android.internal.telephony.PhoneNotifier -com.android.internal.telephony.PhoneStateIntentReceiver com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper com.android.internal.telephony.PhoneSubInfoController diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 032e8245ca42..4f3e8ec9fb51 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -45,8 +45,19 @@ public abstract class ActivityManagerInternal { // Access modes for handleIncomingUser. public static final int ALLOW_NON_FULL = 0; + /** + * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} + * if in the same profile group. + * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. + */ public static final int ALLOW_NON_FULL_IN_PROFILE = 1; public static final int ALLOW_FULL_ONLY = 2; + /** + * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} + * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group. + * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. + */ + public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3; /** * Verify that calling app has access to the given provider. diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 700b3c1b620e..e5c046c2376c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -68,6 +68,7 @@ import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; import android.view.IRecentsAnimationRunner; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.view.WindowContainerTransaction; @@ -121,6 +122,9 @@ interface IActivityTaskManager { in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, IBinder permissionToken, boolean ignoreTargetSecurity, int userId); + + void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode); + boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType, int userId); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 86f52af1a13b..fcdb7cc0855d 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -94,8 +94,11 @@ interface INotificationManager void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group); void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel); NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId); + NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId); + void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId); NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted); void deleteNotificationChannel(String pkg, String channelId); + void deleteConversationNotificationChannels(String pkg, int uid, String conversationId); ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId); ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 3eee1ae5c00c..a33c2c19d05c 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.media.AudioAttributes; import android.net.Uri; import android.os.Parcel; @@ -57,6 +58,22 @@ public final class NotificationChannel implements Parcelable { public static final String DEFAULT_CHANNEL_ID = "miscellaneous"; /** + * The formatter used by the system to create an id for notification + * channels when it automatically creates conversation channels on behalf of an app. The format + * string takes two arguments, in this order: the + * {@link #getId()} of the original notification channel, and the + * {@link ShortcutInfo#getId() id} of the conversation. + */ + public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s"; + + /** + * TODO: STOPSHIP remove + * Conversation id to use for apps that aren't providing them yet. + * @hide + */ + public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id"; + + /** * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this * limit. */ @@ -85,6 +102,8 @@ public final class NotificationChannel implements Parcelable { private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system"; private static final String ATT_ALLOW_BUBBLE = "can_bubble"; private static final String ATT_ORIG_IMP = "orig_imp"; + private static final String ATT_PARENT_CHANNEL = "parent"; + private static final String ATT_CONVERSATION_ID = "conv_id"; private static final String DELIMITER = ","; /** @@ -147,7 +166,7 @@ public final class NotificationChannel implements Parcelable { private static final boolean DEFAULT_ALLOW_BUBBLE = true; @UnsupportedAppUsage - private final String mId; + private String mId; private String mName; private String mDesc; private int mImportance = DEFAULT_IMPORTANCE; @@ -172,6 +191,8 @@ public final class NotificationChannel implements Parcelable { private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE; private boolean mImportanceLockedByOEM; private boolean mImportanceLockedDefaultApp; + private String mParentId = null; + private String mConversationId = null; /** * Creates a notification channel. @@ -236,6 +257,8 @@ public final class NotificationChannel implements Parcelable { mAllowBubbles = in.readBoolean(); mImportanceLockedByOEM = in.readBoolean(); mOriginalImportance = in.readInt(); + mParentId = in.readString(); + mConversationId = in.readString(); } @Override @@ -291,6 +314,8 @@ public final class NotificationChannel implements Parcelable { dest.writeBoolean(mAllowBubbles); dest.writeBoolean(mImportanceLockedByOEM); dest.writeInt(mOriginalImportance); + dest.writeString(mParentId); + dest.writeString(mConversationId); } /** @@ -363,6 +388,13 @@ public final class NotificationChannel implements Parcelable { // Modifiable by apps on channel creation. /** + * @hide + */ + public void setId(String id) { + mId = id; + } + + /** * Sets what group this channel belongs to. * * Group information is only used for presentation, not for behavior. @@ -502,6 +534,23 @@ public final class NotificationChannel implements Parcelable { } /** + * Sets this channel as being person-centric. Different settings and functionality may be + * exposed for people-centric channels. + * + * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of + * this type would be posted to in absence of a specific conversation id. + * For example, if this channel represents 'Messages from Person A', the + * parent channel would be 'Messages.' + * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this + * channel's conversation. + */ + public void setConversationId(@Nullable String parentChannelId, + @Nullable String conversationId) { + mParentId = parentChannelId; + mConversationId = conversationId; + } + + /** * Returns the id of this channel. */ public String getId() { @@ -622,6 +671,22 @@ public final class NotificationChannel implements Parcelable { } /** + * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's + * a conversation related channel. See {@link #setConversationId(String, String)}. + */ + public @Nullable String getParentChannelId() { + return mParentId; + } + + /** + * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's + * associated with a conversation. See {@link #setConversationId(String, String)}. + */ + public @Nullable String getConversationId() { + return mConversationId; + } + + /** * @hide */ @SystemApi @@ -761,6 +826,8 @@ public final class NotificationChannel implements Parcelable { setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false)); setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE)); + setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null), + parser.getAttributeValue(ATT_CONVERSATION_ID, null)); } @Nullable @@ -885,6 +952,12 @@ public final class NotificationChannel implements Parcelable { if (getOriginalImportance() != DEFAULT_IMPORTANCE) { out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance())); } + if (getParentChannelId() != null) { + out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId()); + } + if (getConversationId() != null) { + out.attribute(null, ATT_CONVERSATION_ID, getConversationId()); + } // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of // truth and so aren't written to this xml file @@ -1042,7 +1115,9 @@ public final class NotificationChannel implements Parcelable { && Objects.equals(getAudioAttributes(), that.getAudioAttributes()) && mImportanceLockedByOEM == that.mImportanceLockedByOEM && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp - && mOriginalImportance == that.mOriginalImportance; + && mOriginalImportance == that.mOriginalImportance + && Objects.equals(getParentChannelId(), that.getParentChannelId()) + && Objects.equals(getConversationId(), that.getConversationId()); } @Override @@ -1052,7 +1127,8 @@ public final class NotificationChannel implements Parcelable { getUserLockedFields(), isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(), getAudioAttributes(), isBlockableSystem(), mAllowBubbles, - mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance); + mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance, + mParentId, mConversationId); result = 31 * result + Arrays.hashCode(mVibration); return result; } @@ -1063,26 +1139,7 @@ public final class NotificationChannel implements Parcelable { String output = "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + redactedName - + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") - + ", mImportance=" + mImportance - + ", mBypassDnd=" + mBypassDnd - + ", mLockscreenVisibility=" + mLockscreenVisibility - + ", mSound=" + mSound - + ", mLights=" + mLights - + ", mLightColor=" + mLightColor - + ", mVibration=" + Arrays.toString(mVibration) - + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields) - + ", mFgServiceShown=" + mFgServiceShown - + ", mVibrationEnabled=" + mVibrationEnabled - + ", mShowBadge=" + mShowBadge - + ", mDeleted=" + mDeleted - + ", mGroup='" + mGroup + '\'' - + ", mAudioAttributes=" + mAudioAttributes - + ", mBlockableSystem=" + mBlockableSystem - + ", mAllowBubbles=" + mAllowBubbles - + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM - + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp - + ", mOriginalImp=" + mOriginalImportance + + getFieldsString() + '}'; pw.println(prefix + output); } @@ -1092,7 +1149,12 @@ public final class NotificationChannel implements Parcelable { return "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + mName - + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + + getFieldsString() + + '}'; + } + + private String getFieldsString() { + return ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + ", mImportance=" + mImportance + ", mBypassDnd=" + mBypassDnd + ", mLockscreenVisibility=" + mLockscreenVisibility @@ -1112,7 +1174,8 @@ public final class NotificationChannel implements Parcelable { + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + ", mOriginalImp=" + mOriginalImportance - + '}'; + + ", mParent=" + mParentId + + ", mConversationId" + mConversationId; } /** @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index fdbb8bb0d4c5..61c109885e05 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -843,6 +843,25 @@ public class NotificationManager { } /** + * Returns the notification channel settings for a given channel and conversation id. + * + * <p>The channel must belong to your package, or to a package you are an approved notification + * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query + * a channel as a notification delegate, call this method from a context created for that + * package (see {@link Context#createPackageContext(String, int)}).</p> + */ + public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId, + @NonNull String conversationId) { + INotificationManager service = getService(); + try { + return service.getConversationNotificationChannel(mContext.getOpPackageName(), + mContext.getUserId(), mContext.getPackageName(), channelId, conversationId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns all notification channels belonging to the calling package. * * <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index 84263749232d..dde6dda8e448 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -26,7 +26,6 @@ import android.content.Context; import android.os.IBinder; import android.os.IPullAtomCallback; import android.os.IPullAtomResultReceiver; -import android.os.IStatsCompanionService; import android.os.IStatsManagerService; import android.os.IStatsPullerCallback; import android.os.IStatsd; @@ -61,9 +60,6 @@ public final class StatsManager { private IStatsd mService; @GuardedBy("sLock") - private IStatsCompanionService mStatsCompanion; - - @GuardedBy("sLock") private IStatsManagerService mStatsManagerService; /** @@ -538,7 +534,7 @@ public final class StatsManager { } synchronized (sLock) { try { - IStatsCompanionService service = getIStatsCompanionServiceLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); PullAtomCallbackInternal rec = new PullAtomCallbackInternal(atomTag, callback, executor); service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields, @@ -560,7 +556,7 @@ public final class StatsManager { public void unregisterPullAtomCallback(int atomTag) { synchronized (sLock) { try { - IStatsCompanionService service = getIStatsCompanionServiceLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); service.unregisterPullAtomCallback(atomTag); } catch (RemoteException e) { throw new RuntimeException("Unable to unregister pull atom callback"); @@ -746,16 +742,6 @@ public final class StatsManager { } @GuardedBy("sLock") - private IStatsCompanionService getIStatsCompanionServiceLocked() { - if (mStatsCompanion != null) { - return mStatsCompanion; - } - mStatsCompanion = IStatsCompanionService.Stub.asInterface( - ServiceManager.getService("statscompanion")); - return mStatsCompanion; - } - - @GuardedBy("sLock") private IStatsManagerService getIStatsManagerServiceLocked() { if (mStatsManagerService != null) { return mStatsManagerService; diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 078e4538c66b..42563b5a1561 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -267,6 +267,7 @@ public class StatusBarManager { * @hide */ @UnsupportedAppUsage + @TestApi public void expandNotificationsPanel() { try { final IStatusBarService svc = getService(); @@ -284,6 +285,7 @@ public class StatusBarManager { * @hide */ @UnsupportedAppUsage + @TestApi public void collapsePanels() { try { final IStatusBarService svc = getService(); diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index f299d456a18f..e6c89d9071e6 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -17,9 +17,12 @@ package android.app.admin; import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.Intent; +import android.os.UserHandle; import java.util.List; +import java.util.Set; /** * Device policy manager local system service interface. @@ -165,4 +168,23 @@ public abstract class DevicePolicyManagerInternal { * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead. */ protected abstract DeviceStateCache getDeviceStateCache(); + + /** + * Returns the combined set of the following: + * <ul> + * <li>The package names that the admin has previously set as allowed to request user consent + * for cross-profile communication, via {@link + * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li> + * <li>The default package names that are allowed to request user consent for cross-profile + * communication without being explicitly enabled by the admin , via {@link + * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li> + * </ul> + * + * @return the combined set of whitelisted package names set via + * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and + * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)} + * + * @hide + */ + public abstract List<String> getAllCrossProfilePackages(); } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index b1b6f0d61f9f..cb1f05573d93 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -2670,6 +2670,9 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.PAN) { BluetoothPan pan = new BluetoothPan(context, listener); return true; + } else if (profile == BluetoothProfile.PBAP) { + BluetoothPbap pbap = new BluetoothPbap(context, listener); + return true; } else if (profile == BluetoothProfile.HEALTH) { Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated"); return false; @@ -2742,6 +2745,10 @@ public final class BluetoothAdapter { BluetoothPan pan = (BluetoothPan) proxy; pan.close(); break; + case BluetoothProfile.PBAP: + BluetoothPbap pbap = (BluetoothPbap) proxy; + pbap.close(); + break; case BluetoothProfile.GATT: BluetoothGatt gatt = (BluetoothGatt) proxy; gatt.close(); diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java index e9b0be2c4cd6..a923be62fbce 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -16,8 +16,12 @@ package android.bluetooth; +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -36,6 +40,7 @@ import java.util.concurrent.Executor; */ public final class BluetoothHidDevice implements BluetoothProfile { private static final String TAG = BluetoothHidDevice.class.getSimpleName(); + private static final boolean DBG = false; /** * Intent used to broadcast the change in connection state of the Input Host profile. @@ -682,4 +687,62 @@ public final class BluetoothHidDevice implements BluetoothProfile { return result; } + + /** + * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} + * and disconnects Hid device if connectionPolicy is + * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}. + * + * <p> The device should already be paired. + * Connection policy can be one of: + * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, + * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, + * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy determines whether hid device should be connected or disconnected + * @return true if hid device is connected or disconnected, false otherwise + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + try { + final IBluetoothHidDevice service = getService(); + if (service != null && isEnabled() + && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + if (DBG) { + Log.d(TAG, msg); + } + } } diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 917e7fa04e4c..467470674286 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -17,7 +17,10 @@ package android.bluetooth; import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -35,21 +38,35 @@ import java.util.List; * * @hide */ +@SystemApi public final class BluetoothMap implements BluetoothProfile { private static final String TAG = "BluetoothMap"; private static final boolean DBG = true; private static final boolean VDBG = false; + /** @hide */ + @SuppressLint("ActionValue") + @SystemApi public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; - /** There was an error trying to obtain the state */ + /** + * There was an error trying to obtain the state + * + * @hide + */ public static final int STATE_ERROR = -1; + /** @hide */ public static final int RESULT_FAILURE = 0; + /** @hide */ public static final int RESULT_SUCCESS = 1; - /** Connection canceled before completion. */ + /** + * Connection canceled before completion. + * + * @hide + */ public static final int RESULT_CANCELED = 2; private BluetoothAdapter mAdapter; @@ -71,6 +88,7 @@ public final class BluetoothMap implements BluetoothProfile { mProfileConnector.connect(context, listener); } + @SuppressLint("GenericException") protected void finalize() throws Throwable { try { close(); @@ -84,6 +102,8 @@ public final class BluetoothMap implements BluetoothProfile { * Other public functions of BluetoothMap will return default error * results once close() has been called. Multiple invocations of close() * are ok. + * + * @hide */ public synchronized void close() { mProfileConnector.disconnect(); @@ -98,6 +118,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not * connected to the Map service. + * + * @hide */ public int getState() { if (VDBG) log("getState()"); @@ -120,6 +142,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @return The remote Bluetooth device, or null if not in connected or connecting state, or if * this proxy object is not connected to the Map service. + * + * @hide */ public BluetoothDevice getClient() { if (VDBG) log("getClient()"); @@ -141,6 +165,8 @@ public final class BluetoothMap implements BluetoothProfile { * Returns true if the specified Bluetooth device is connected. * Returns false if not connected, or if this proxy object is not * currently connected to the Map service. + * + * @hide */ public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); @@ -161,6 +187,8 @@ public final class BluetoothMap implements BluetoothProfile { /** * Initiate connection. Initiation of outgoing connections is not * supported for MAP server. + * + * @hide */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); @@ -172,6 +200,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @param device Remote Bluetooth Device * @return false on error, true otherwise + * + * @hide */ @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { @@ -196,6 +226,8 @@ public final class BluetoothMap implements BluetoothProfile { * devices. It tries to err on the side of false positives. * * @return True if this device might support Map. + * + * @hide */ public static boolean doesClassMatchSink(BluetoothClass btClass) { // TODO optimize the rule @@ -214,8 +246,11 @@ public final class BluetoothMap implements BluetoothProfile { * Get the list of connected devices. Currently at most one. * * @return list of connected devices + * + * @hide */ - public List<BluetoothDevice> getConnectedDevices() { + @SystemApi + public @NonNull List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); final IBluetoothMap service = getService(); if (service != null && isEnabled()) { @@ -234,6 +269,8 @@ public final class BluetoothMap implements BluetoothProfile { * Get the list of devices matching specified states. Currently at most one. * * @return list of matching devices + * + * @hide */ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); @@ -254,6 +291,8 @@ public final class BluetoothMap implements BluetoothProfile { * Get connection state of device * * @return device connection state + * + * @hide */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); @@ -301,7 +340,7 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -349,7 +388,7 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 42f27f2a9c98..024bb06098ab 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -16,9 +16,11 @@ package android.bluetooth; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; @@ -256,6 +258,41 @@ public final class BluetoothPan implements BluetoothProfile { } /** + * Set connection policy of the profile + * + * <p> The device should already be paired. + * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, + * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy is the connection policy to set to for this profile + * @return true if connectionPolicy is set, false on error + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + try { + final IBluetoothPan service = getService(); + if (service != null && isEnabled() + && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + /** * {@inheritDoc} */ @Override diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index 948885ed5010..e07ca521e77d 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -274,15 +274,15 @@ public class BluetoothPbap implements BluetoothProfile { } /** - * Pbap does not store connection policy, so this function only disconnects Pbap if - * connectionPolicy is CONNECTION_POLICY_FORBIDDEN. + * Pbap does not store connection policy, so this function only disconnects pbap if + * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}. * * <p> The device should already be paired. * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} * * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile + * @param connectionPolicy determines whether to disconnect the device * @return true if pbap is successfully disconnected, false otherwise * @hide */ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5cb2907dfba4..1b40a184c52b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3240,15 +3240,40 @@ public abstract class Context { } /** - * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle - * argument for use by system server and other multi-user aware code. - * @hide + * Binds to a service in the given {@code user} in the same manner as + * {@link #bindService(Intent, ServiceConnection, int)}. + * + * <p>If the given {@code user} is in the same profile group and the target package is the + * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is + * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS} + * for interacting with other users. + * + * @param service Identifies the service to connect to. The Intent must + * specify an explicit component name. + * @param conn Receives information as the service is started and stopped. + * This must be a valid ServiceConnection object; it must not be null. + * @param flags Operation options for the binding. May be 0, + * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND}, + * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT}, + * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}. + * {@link #BIND_IMPORTANT}, or + * {@link #BIND_ADJUST_WITH_ACTIVITY}. + * @return {@code true} if the system is in the process of bringing up a + * service that your client has permission to bind to; {@code false} + * if the system couldn't find the service. If this value is {@code true}, you + * should later call {@link #unbindService} to release the + * connection. + * + * @throws SecurityException if the client does not have the required permission to bind. */ - @SystemApi @SuppressWarnings("unused") - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) - public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn, - int flags, UserHandle user) { + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) + public boolean bindServiceAsUser( + @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags, + @NonNull UserHandle user) { throw new RuntimeException("Not implemented. Must override in a subclass."); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 3bb0f9222237..c8f587f71bca 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6440,19 +6440,22 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000; /** - * If set, when sending a broadcast <i>before boot has completed</i> only + * If set, when sending a broadcast <i>before the system has fully booted up + * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only * registered receivers will be called -- no BroadcastReceiver components * will be launched. Sticky intent state will be recorded properly even * if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY} * is specified in the broadcast intent, this flag is unnecessary. * - * <p>This flag is only for use by system sevices as a convenience to - * avoid having to implement a more complex mechanism around detection + * <p>This flag is only for use by system services (even services from mainline modules) as a + * convenience to avoid having to implement a more complex mechanism around detection * of boot completion. * + * <p>This is useful to system server mainline modules + * * @hide */ - @UnsupportedAppUsage + @SystemApi public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000; /** * Set when this broadcast is for a boot upgrade, a special mode that diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index e897b917fcc2..9d5751480a80 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -16,20 +16,25 @@ package android.content.pm; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import com.android.internal.R; import com.android.internal.util.UserIcons; import java.util.List; +import java.util.Set; /** * Class for handling cross profile operations. Apps can use this class to interact with its @@ -169,6 +174,86 @@ public class CrossProfileApps { } } + /** + * Returns whether the calling package can request to interact across profiles. + * + * <p>The package's current ability to interact across profiles can be checked with + * {@link #canInteractAcrossProfiles()}. + * + * <p>Specifically, returns whether the following are all true: + * <ul> + * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> + * <li>The calling app has requested</li> + * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest. + * <li>The calling package has either been whitelisted by default by the OEM or has been + * explicitly whitelisted by the admin via + * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}. + * </li> + * </ul> + * + * @return true if the calling package can request to interact across profiles. + */ + public boolean canRequestInteractAcrossProfiles() { + try { + return mService.canRequestInteractAcrossProfiles(mContext.getPackageName()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the calling package can interact across profiles. + * + * <p>The package's current ability to request to interact across profiles can be checked with + * {@link #canRequestInteractAcrossProfiles()}. + * + * <p>Specifically, returns whether the following are all true: + * <ul> + * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> + * <li>The user has previously consented to cross-profile communication for the calling + * package.</li> + * <li>The calling package has either been whitelisted by default by the OEM or has been + * explicitly whitelisted by the admin via + * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}. + * </li> + * </ul> + * + * @return true if the calling package can interact across profiles. + * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the + * calling UID. + */ + public boolean canInteractAcrossProfiles() { + try { + return mService.canInteractAcrossProfiles(mContext.getPackageName()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Returns an {@link Intent} to open the settings page that allows the user to decide whether + * the calling app can interact across profiles. The current state is given by + * {@link #canInteractAcrossProfiles()}. + * + * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}. + * + * @return an {@link Intent} to open the settings page that allows the user to decide whether + * the app can interact across profiles + * + * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the + * calling UID. + */ + public @Nullable Intent createRequestInteractAcrossProfilesIntent() { + if (!canRequestInteractAcrossProfiles()) { + return null; + } + final Intent settingsIntent = new Intent(); + settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS); + final Uri packageUri = Uri.parse("package:" + mContext.getPackageName()); + settingsIntent.setData(packageUri); + return settingsIntent; + } + private void verifyCanAccessUser(UserHandle userHandle) { if (!getTargetUserProfiles().contains(userHandle)) { throw new SecurityException("Not allowed to access " + userHandle); diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index d2d66cbda610..c5db0ccebf52 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -30,4 +30,6 @@ interface ICrossProfileApps { void startActivityAsUser(in IApplicationThread caller, in String callingPackage, in ComponentName component, int userId, boolean launchMainActivity); List<UserHandle> getTargetUserProfiles(in String callingPackage); + boolean canInteractAcrossProfiles(in String callingPackage); + boolean canRequestInteractAcrossProfiles(in String callingPackage); }
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b85c58ad0e2f..4bfc40e698b9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2323,6 +2323,13 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * has a telephony radio that support data. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; + + /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device supports telephony carrier restriction mechanism. * diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java index 6378db0ebbbd..b273cd67479c 100644 --- a/core/java/android/content/rollback/PackageRollbackInfo.java +++ b/core/java/android/content/rollback/PackageRollbackInfo.java @@ -76,6 +76,11 @@ public final class PackageRollbackInfo implements Parcelable { */ private final boolean mIsApex; + /** + * Whether this instance represents the PackageRollbackInfo for an APK in APEX. + */ + private final boolean mIsApkInApex; + /* * The list of users for which snapshots have been saved. */ @@ -157,6 +162,10 @@ public final class PackageRollbackInfo implements Parcelable { public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() { return mRollbackDataPolicy; } + /** @hide */ + public boolean isApkInApex() { + return mIsApkInApex; + } /** @hide */ public IntArray getSnapshottedUsers() { @@ -190,17 +199,18 @@ public final class PackageRollbackInfo implements Parcelable { public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, - boolean isApex, @NonNull IntArray snapshottedUsers, + boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers, @NonNull SparseLongArray ceSnapshotInodes) { this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex, - snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE); + isApkInApex, snapshottedUsers, ceSnapshotInodes, + PackageManager.RollbackDataPolicy.RESTORE); } /** @hide */ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, - boolean isApex, @NonNull IntArray snapshottedUsers, + boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers, @NonNull SparseLongArray ceSnapshotInodes, @PackageManager.RollbackDataPolicy int rollbackDataPolicy) { this.mVersionRolledBackFrom = packageRolledBackFrom; @@ -209,6 +219,7 @@ public final class PackageRollbackInfo implements Parcelable { this.mPendingRestores = pendingRestores; this.mIsApex = isApex; this.mRollbackDataPolicy = rollbackDataPolicy; + this.mIsApkInApex = isApkInApex; this.mSnapshottedUsers = snapshottedUsers; this.mCeSnapshotInodes = ceSnapshotInodes; } @@ -217,6 +228,7 @@ public final class PackageRollbackInfo implements Parcelable { this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in); this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in); this.mIsApex = in.readBoolean(); + this.mIsApkInApex = in.readBoolean(); this.mPendingRestores = null; this.mPendingBackups = null; this.mSnapshottedUsers = null; @@ -234,6 +246,7 @@ public final class PackageRollbackInfo implements Parcelable { mVersionRolledBackFrom.writeToParcel(out, flags); mVersionRolledBackTo.writeToParcel(out, flags); out.writeBoolean(mIsApex); + out.writeBoolean(mIsApkInApex); } public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR = diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 8231c58a105e..43f3787e15da 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -16,6 +16,7 @@ package android.hardware.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.ModelParams; import android.media.AudioFormat; import android.media.audio.common.AudioConfig; @@ -32,8 +33,6 @@ import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; -import android.annotation.Nullable; - import java.util.Arrays; import java.util.UUID; @@ -48,6 +47,7 @@ class ConversionUtil { properties.description, properties.uuid, properties.version, + properties.supportedModelArch, properties.maxSoundModels, properties.maxKeyPhrases, properties.maxUsers, diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 55505ba2e278..d87200931830 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -101,6 +101,14 @@ public class SoundTrigger { /** Voice detection engine version */ public final int version; + /** + * String naming the architecture used for running the supported models. + * (eg. a platform running models on a DSP could implement this string to convey the DSP + * architecture used) + */ + @NonNull + public final String supportedModelArch; + /** Maximum number of active sound models */ public final int maxSoundModels; @@ -130,15 +138,17 @@ public class SoundTrigger { public final boolean returnsTriggerInEvent; ModuleProperties(int id, @NonNull String implementor, @NonNull String description, - @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases, - int maxUsers, int recognitionModes, boolean supportsCaptureTransition, - int maxBufferMs, boolean supportsConcurrentCapture, - int powerConsumptionMw, boolean returnsTriggerInEvent) { + @NonNull String uuid, int version, @NonNull String supportedModelArch, + int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes, + boolean supportsCaptureTransition, int maxBufferMs, + boolean supportsConcurrentCapture, int powerConsumptionMw, + boolean returnsTriggerInEvent) { this.id = id; this.implementor = requireNonNull(implementor); this.description = requireNonNull(description); this.uuid = UUID.fromString(requireNonNull(uuid)); this.version = version; + this.supportedModelArch = requireNonNull(supportedModelArch); this.maxSoundModels = maxSoundModels; this.maxKeyphrases = maxKeyphrases; this.maxUsers = maxUsers; @@ -167,6 +177,7 @@ public class SoundTrigger { String description = in.readString(); String uuid = in.readString(); int version = in.readInt(); + String supportedModelArch = in.readString(); int maxSoundModels = in.readInt(); int maxKeyphrases = in.readInt(); int maxUsers = in.readInt(); @@ -177,7 +188,7 @@ public class SoundTrigger { int powerConsumptionMw = in.readInt(); boolean returnsTriggerInEvent = in.readByte() == 1; return new ModuleProperties(id, implementor, description, uuid, version, - maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, + supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture, powerConsumptionMw, returnsTriggerInEvent); } @@ -189,6 +200,7 @@ public class SoundTrigger { dest.writeString(description); dest.writeString(uuid.toString()); dest.writeInt(version); + dest.writeString(supportedModelArch); dest.writeInt(maxSoundModels); dest.writeInt(maxKeyphrases); dest.writeInt(maxUsers); @@ -208,7 +220,8 @@ public class SoundTrigger { @Override public String toString() { return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description=" - + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels=" + + description + ", uuid=" + uuid + ", version=" + version + + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels=" + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers=" + maxUsers + ", recognitionModes=" + recognitionModes + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs=" @@ -588,19 +601,19 @@ public class SoundTrigger { } } - /***************************************************************************** + /** * A ModelParamRange is a representation of supported parameter range for a * given loaded model. - ****************************************************************************/ + */ public static final class ModelParamRange implements Parcelable { /** - * start of supported range inclusive + * The inclusive start of supported range. */ public final int start; /** - * end of supported range inclusive + * The inclusive end of supported range. */ public final int end; @@ -609,31 +622,65 @@ public class SoundTrigger { this.end = end; } + /** @hide */ private ModelParamRange(@NonNull Parcel in) { this.start = in.readInt(); this.end = in.readInt(); } @NonNull - public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() { - @Override - @NonNull - public ModelParamRange createFromParcel(@NonNull Parcel in) { - return new ModelParamRange(in); - } - - @Override - @NonNull - public ModelParamRange[] newArray(int size) { - return new ModelParamRange[size]; - } - }; + public static final Creator<ModelParamRange> CREATOR = + new Creator<ModelParamRange>() { + @Override + @NonNull + public ModelParamRange createFromParcel(@NonNull Parcel in) { + return new ModelParamRange(in); + } + + @Override + @NonNull + public ModelParamRange[] newArray(int size) { + return new ModelParamRange[size]; + } + }; + /** @hide */ @Override public int describeContents() { return 0; } + /** @hide */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (start); + result = prime * result + (end); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ModelParamRange other = (ModelParamRange) obj; + if (start != other.start) { + return false; + } + if (end != other.end) { + return false; + } + return true; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(start); diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java index 98bab44e19fb..912df67a0b45 100644 --- a/core/java/android/net/DhcpInfo.java +++ b/core/java/android/net/DhcpInfo.java @@ -16,8 +16,8 @@ package android.net; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; /** * A simple object for retrieving the results of a DHCP request. @@ -67,12 +67,12 @@ public class DhcpInfo implements Parcelable { buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress()); } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public int describeContents() { return 0; } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(ipAddress); dest.writeInt(gateway); @@ -83,7 +83,7 @@ public class DhcpInfo implements Parcelable { dest.writeInt(leaseDuration); } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR = new Creator<DhcpInfo>() { public DhcpInfo createFromParcel(Parcel in) { diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 45d0c7313fca..09ec6c35fcb9 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,7 +17,6 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -26,6 +25,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; +import android.net.annotations.PolicyDirection; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -41,8 +41,6 @@ import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; @@ -78,11 +76,6 @@ public final class IpSecManager { */ public static final int DIRECTION_OUT = 1; - /** @hide */ - @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) - @Retention(RetentionPolicy.SOURCE) - public @interface PolicyDirection {} - /** * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 33c39d489835..739e8178e68e 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -587,15 +587,14 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are - * typically provided by restricted networks. + * Deduces that all the capabilities it provides are typically provided by restricted networks + * or not. * - * TODO: consider: - * - Renaming it to guessRestrictedCapability and make it set the - * restricted capability bit in addition to clearing it. + * @return {@code true} if the network should be restricted. * @hide */ - public void maybeMarkCapabilitiesRestricted() { + @SystemApi + public boolean deduceRestrictedCapability() { // Check if we have any capability that forces the network to be restricted. final boolean forceRestrictedCapability = (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0; @@ -609,8 +608,17 @@ public final class NetworkCapabilities implements Parcelable { final boolean hasRestrictedCapabilities = (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0; - if (forceRestrictedCapability - || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) { + return forceRestrictedCapability + || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities); + } + + /** + * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted. + * + * @hide + */ + public void maybeMarkCapabilitiesRestricted() { + if (deduceRestrictedCapability()) { removeCapability(NET_CAPABILITY_NOT_RESTRICTED); } } diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java new file mode 100644 index 000000000000..febd9b406111 --- /dev/null +++ b/core/java/android/net/annotations/PolicyDirection.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 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 android.net.annotations; + +import android.annotation.IntDef; +import android.net.IpSecManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * IPsec traffic direction. + * + * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class + * to allow others to statically include it. + * + * @hide + */ +@IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT}) +@Retention(RetentionPolicy.SOURCE) +public @interface PolicyDirection {} diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 3ef86ed9f994..5d80ab6453cc 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -68,10 +68,9 @@ public class Process { public static final int LOG_UID = 1007; /** - * Defines the UID/GID for the WIFI supplicant process. - * @hide + * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd, + * vendor HAL, etc. */ - @UnsupportedAppUsage public static final int WIFI_UID = 1010; /** @@ -758,11 +757,12 @@ public class Process { /** * Set the priority of a thread, based on Linux priorities. - * - * @param tid The identifier of the thread/process to change. + * + * @param tid The identifier of the thread/process to change. It should be + * the native thread id but not the managed id of {@link java.lang.Thread}. * @param priority A Linux priority level, from -20 for highest scheduling * priority to 19 for lowest scheduling priority. - * + * * @throws IllegalArgumentException Throws IllegalArgumentException if * <var>tid</var> does not exist. * @throws SecurityException Throws SecurityException if your process does diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index 16f7b398731c..73e1adf134f2 100644 --- a/core/java/android/os/UpdateEngine.java +++ b/core/java/android/os/UpdateEngine.java @@ -19,6 +19,7 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.res.AssetFileDescriptor; import android.os.IUpdateEngine; import android.os.IUpdateEngineCallback; import android.os.RemoteException; @@ -349,16 +350,17 @@ public class UpdateEngine { } /** - * Applies the payload passed as ParcelFileDescriptor {@code pfd} instead of - * using the {@code file://} scheme. + * Applies the payload passed as AssetFileDescriptor {@code assetFd} + * instead of using the {@code file://} scheme. * * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and * {@code headerKeyValuePairs} parameters. */ - public void applyPayload(@NonNull ParcelFileDescriptor pfd, long offset, long size, + public void applyPayload(@NonNull AssetFileDescriptor assetFd, @NonNull String[] headerKeyValuePairs) { try { - mUpdateEngine.applyPayloadFd(pfd, offset, size, headerKeyValuePairs); + mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(), + assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 6e199ce3a73f..d8fadfb41189 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1850,10 +1850,7 @@ public class UserManager { * Checks if the calling app is running in a managed profile. * * @return whether the caller is in a managed profile. - * @hide */ - @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile() { // No need for synchronization. Once it becomes non-null, it'll be non-null forever. // Worst case we might end up calling the AIDL method multiple times but that's fine. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9ee4cc30949d..a31c3d1749c6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -389,6 +389,21 @@ public final class Settings { "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; /** + * Activity Action: Show settings to allow configuration of cross-profile access for apps + * + * Input: Optionally, the Intent's data URI can specify the application package name to + * directly invoke the management GUI specific to the package name. For example + * "package:com.my.app". + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS = + "android.settings.MANAGE_CROSS_PROFILE_ACCESS"; + + /** * Activity Action: Show the "Open by Default" page in a particular application's details page. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard against this. @@ -14404,6 +14419,42 @@ public final class Settings { }; /** + * Activity Action: Show screen for controlling which apps have access to manage external + * storage. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * If you want to control a specific app's access to manage external storage, use + * {@link #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION} instead. + * <p> + * Output: Nothing. + * @see #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = + "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"; + + /** + * Activity Action: Show screen for controlling if the app specified in the data URI of the + * intent can manage external storage. + * <p> + * Launching the corresponding activity requires the permission + * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * Input: The Intent's data URI MUST specify the application package name whose ability of + * managing external storage you want to control. + * For example "package:com.my.app". + * <p> + * Output: Nothing. + * @see #ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = + "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; + + /** * Performs a strict and comprehensive check of whether a calling package is allowed to * write/modify system settings, as the condition differs for pre-M, M+, and * privileged/preinstalled apps. If the provided uid does not match the @@ -14429,8 +14480,9 @@ public final class Settings { * current time. * @hide */ - public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid, - String callingPackage, boolean throwException) { + @SystemApi + public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, boolean throwException) { return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid, callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS, PM_WRITE_SETTINGS, true); diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 5977bafd7cf1..4ead3fc67260 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -49,6 +49,9 @@ oneway interface INotificationListener void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); void onNotificationsSeen(in List<String> keys); + void onPanelRevealed(int items); + void onPanelHidden(); + void onNotificationVisibilityChanged(String key, boolean isVisible); void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); void onNotificationDirectReply(String key); void onSuggestedReplySent(String key, in CharSequence reply, int source); diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index da4025419656..e976e18602c1 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -182,6 +182,32 @@ public abstract class NotificationAssistantService extends NotificationListenerS } /** + * Implement this to know when the notification panel is revealed + * + * @param items Number of items on the panel at time of opening + */ + public void onPanelRevealed(int items) { + + } + + /** + * Implement this to know when the notification panel is hidden + */ + public void onPanelHidden() { + + } + + /** + * Implement this to know when a notification becomes visible or hidden from the user. + * + * @param key the notification key + * @param isVisible whether the notification is visible. + */ + public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) { + + } + + /** * Implement this to know when a notification change (expanded / collapsed) is visible to user. * * @param key the notification key @@ -337,6 +363,30 @@ public abstract class NotificationAssistantService extends NotificationListenerS } @Override + public void onPanelRevealed(int items) { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = items; + mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED, + args).sendToTarget(); + } + + @Override + public void onPanelHidden() { + SomeArgs args = SomeArgs.obtain(); + mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN, + args).sendToTarget(); + } + + @Override + public void onNotificationVisibilityChanged(String key, boolean isVisible) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = key; + args.argi1 = isVisible ? 1 : 0; + mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED, + args).sendToTarget(); + } + + @Override public void onNotificationExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { SomeArgs args = SomeArgs.obtain(); @@ -394,6 +444,9 @@ public abstract class NotificationAssistantService extends NotificationListenerS public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6; public static final int MSG_ON_ACTION_INVOKED = 7; public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8; + public static final int MSG_ON_PANEL_REVEALED = 9; + public static final int MSG_ON_PANEL_HIDDEN = 10; + public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11; public MyHandler(Looper looper) { super(looper, null, false); @@ -480,6 +533,25 @@ public abstract class NotificationAssistantService extends NotificationListenerS onAllowedAdjustmentsChanged(); break; } + case MSG_ON_PANEL_REVEALED: { + SomeArgs args = (SomeArgs) msg.obj; + int items = args.argi1; + args.recycle(); + onPanelRevealed(items); + break; + } + case MSG_ON_PANEL_HIDDEN: { + onPanelHidden(); + break; + } + case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + String key = (String) args.arg1; + boolean isVisible = args.argi1 == 1; + args.recycle(); + onNotificationVisibilityChanged(key, isVisible); + break; + } } } } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 80d054b700f8..fd04f499a432 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1383,6 +1383,22 @@ public abstract class NotificationListenerService extends Service { } @Override + public void onPanelRevealed(int items) throws RemoteException { + // no-op in the listener + } + + @Override + public void onPanelHidden() throws RemoteException { + // no-op in the listener + } + + @Override + public void onNotificationVisibilityChanged( + String key, boolean isVisible) { + // no-op in the listener + } + + @Override public void onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId) throws RemoteException { diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 67925bfea2c2..d7c6d0f265c6 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -168,6 +168,22 @@ public class AlwaysOnHotwordDetector { public static final int RECOGNITION_MODE_USER_IDENTIFICATION = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = { + MODEL_PARAM_THRESHOLD_FACTOR, + }) + public @interface ModelParams {} + + /** + * Controls the sensitivity threshold adjustment factor for a given model. + * Negative value corresponds to less sensitive model (high threshold) and + * a positive value corresponds to a more sensitive model (low threshold). + * Default value is 0. + */ + public static final int MODEL_PARAM_THRESHOLD_FACTOR = + android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR; + static final String TAG = "AlwaysOnHotwordDetector"; static final boolean DBG = false; @@ -198,6 +214,53 @@ public class AlwaysOnHotwordDetector { private int mAvailability = STATE_NOT_READY; /** + * A ModelParamRange is a representation of supported parameter range for a + * given loaded model. + */ + public static final class ModelParamRange { + private final SoundTrigger.ModelParamRange mModelParamRange; + + /** @hide */ + ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) { + mModelParamRange = modelParamRange; + } + + /** + * The inclusive start of supported range. + * + * @return start of range + */ + public int start() { + return mModelParamRange.start; + } + + /** + * The inclusive end of supported range. + * + * @return end of range + */ + public int end() { + return mModelParamRange.end; + } + + @Override + @NonNull + public String toString() { + return mModelParamRange.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return mModelParamRange.equals(obj); + } + + @Override + public int hashCode() { + return mModelParamRange.hashCode(); + } + } + + /** * Additional payload for {@link Callback#onDetected}. */ public static class EventPayload { @@ -445,6 +508,83 @@ public class AlwaysOnHotwordDetector { } /** + * Set a model specific {@link ModelParams} with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this + * method. + * + * @param modelParam {@link ModelParams} + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + public int setParameter(@ModelParams int modelParam, int value) { + if (DBG) { + Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("setParameter called on an invalid detector"); + } + + return setParameterLocked(modelParam, value); + } + } + + /** + * Get a model specific {@link ModelParams}. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See {@link ModelParams} for parameter default values. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before + * calling this method. + * + * @param modelParam {@link ModelParams} + * @return value of parameter + */ + public int getParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "getParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("getParameter called on an invalid detector"); + } + + return getParameterLocked(modelParam); + } + } + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter} + * or {@link AlwaysOnHotwordDetector#getParameter}. + * + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public ModelParamRange queryParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "queryParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("queryParameter called on an invalid detector"); + } + + return queryParameterLocked(modelParam); + } + } + + /** * Creates an intent to start the enrollment for the associated keyphrase. * This intent must be invoked using {@link Context#startForegroundService(Intent)}. * Starting re-enrollment is only valid if the keyphrase is un-enrolled, @@ -601,6 +741,47 @@ public class AlwaysOnHotwordDetector { return code; } + private int setParameterLocked(@ModelParams int modelParam, int value) { + try { + int code = mModelManagementService.setParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam, value); + + if (code != STATUS_OK) { + Slog.w(TAG, "setParameter failed with error code " + code); + } + + return code; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private int getParameterLocked(@ModelParams int modelParam) { + try { + return mModelManagementService.getParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@ModelParams int modelParam) { + try { + SoundTrigger.ModelParamRange modelParamRange = + mModelManagementService.queryParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + + if (modelParamRange == null) { + return null; + } + + return new ModelParamRange(modelParamRange); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void notifyStateChangedLocked() { Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); message.arg1 = mAvailability; diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java index b85ef76bd2d5..4c9328a1c372 100644 --- a/core/java/android/text/SpannableStringInternal.java +++ b/core/java/android/text/SpannableStringInternal.java @@ -40,9 +40,10 @@ import java.lang.reflect.Array; if (source instanceof Spanned) { if (source instanceof SpannableStringInternal) { - copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan); + copySpansFromInternal( + (SpannableStringInternal) source, start, end, ignoreNoCopySpan); } else { - copySpans((Spanned) source, start, end, ignoreNoCopySpan); + copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan); } } } @@ -65,7 +66,7 @@ import java.lang.reflect.Array; * @param end End index in the source object. * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source} */ - private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) { + private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) { Object[] spans = src.getSpans(start, end, Object.class); for (int i = 0; i < spans.length; i++) { @@ -94,7 +95,7 @@ import java.lang.reflect.Array; * @param end End index in the source object. * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons. */ - private void copySpans(SpannableStringInternal src, int start, int end, + private void copySpansFromInternal(SpannableStringInternal src, int start, int end, boolean ignoreNoCopySpan) { int count = 0; final int[] srcData = src.mSpanData; @@ -555,12 +556,12 @@ import java.lang.reflect.Array; */ @UnsupportedAppUsage private void copySpans(Spanned src, int start, int end) { - copySpans(src, start, end, false); + copySpansFromSpanned(src, start, end, false); } @UnsupportedAppUsage private void copySpans(SpannableStringInternal src, int start, int end) { - copySpans(src, start, end, false); + copySpansFromInternal(src, start, end, false); } diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl new file mode 100644 index 000000000000..e92aafed6f22 --- /dev/null +++ b/core/java/android/view/ITaskOrganizer.aidl @@ -0,0 +1,38 @@ +/* //device/java/android/android/view/ITaskOrganizer.aidl +** +** 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 android.view; + +import android.view.IWindowContainer; +import android.view.SurfaceControl; +import android.app.ActivityManager; + +/** + * Interface for ActivityTaskManager/WindowManager to delegate control of tasks. + * {@hide} + */ +oneway interface ITaskOrganizer { + void taskAppeared(in IWindowContainer container, + in ActivityManager.RunningTaskInfo taskInfo); + void taskVanished(in IWindowContainer container); + + /** + * Called upon completion of + * ActivityTaskManagerService#applyTaskOrganizerTransaction + */ + void transactionReady(int id, in SurfaceControl.Transaction t); +}
\ No newline at end of file diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 6fdadc60afea..27edb0b69bdd 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -16,17 +16,28 @@ package android.view; +import android.view.InsetsController.LayoutInsetsDuringAnimation; +import android.view.WindowInsetsAnimationCallback.AnimationBounds; +import android.view.WindowInsetsAnimationCallback.InsetsAnimation; + /** * Provide an interface to let InsetsAnimationControlImpl call back into its owner. * @hide */ public interface InsetsAnimationControlCallbacks { + /** - * Dispatch the animation started event to all listeners. - * @param animation + * Executes the necessary code to start the animation in the correct order, including: + * <ul> + * <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li> + * <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li> + * <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li> + * <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li> + * </ul> */ - void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation, - WindowInsetsAnimationCallback.AnimationBounds bounds); + void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation, + AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation); /** * Schedule the apply by posting the animation callback. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index a3245b9916ec..6589e75c7bc2 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ISIDE_BOTTOM; import static android.view.InsetsState.ISIDE_FLOATING; import static android.view.InsetsState.ISIDE_LEFT; @@ -30,6 +32,7 @@ import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseSetArray; +import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.InsetsState.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; @@ -80,7 +83,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, - InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) { + InsetsAnimationControlCallbacks controller, long durationMs, boolean fade, + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { mControls = controls; mListener = listener; mTypes = types; @@ -95,14 +99,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mFrame = new Rect(frame); buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls); - // TODO: Check for controllability first and wait for IME if needed. - listener.onReady(this, types); - mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes, InsetsController.INTERPOLATOR, durationMs); mAnimation.setAlpha(getCurrentAlpha()); - mController.dispatchAnimationStarted(mAnimation, - new AnimationBounds(mHiddenInsets, mShownInsets)); + mController.startAnimation(this, listener, types, mAnimation, + new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation); } @Override diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 2a7a4e3a922c..775490c757d4 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -37,16 +37,20 @@ import android.util.SparseArray; import android.view.InsetsSourceConsumer.ShowResult; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; +import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** @@ -67,6 +71,37 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private @interface AnimationDirection{} /** + * Layout mode during insets animation: The views should be laid out as if the changing inset + * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will + * be called as if the changing insets types are shown, which will result in the views being + * laid out as if the insets are fully shown. + */ + static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0; + + /** + * Layout mode during insets animation: The views should be laid out as if the changing inset + * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will + * be called as if the changing insets types are hidden, which will result in the views being + * laid out as if the insets are fully hidden. + */ + static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1; + + /** + * Determines the behavior of how the views should be laid out during an insets animation that + * is controlled by the application by calling {@link #controlWindowInsetsAnimation}. + * <p> + * When the animation is system-initiated, the layout mode is always chosen such that the + * pre-animation layout will represent the opposite of the starting state, i.e. when insets + * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets + * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN, + LAYOUT_INSETS_DURING_ANIMATION_HIDDEN}) + @interface LayoutInsetsDuringAnimation { + } + + /** * Translation animation evaluator. */ private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of( @@ -109,11 +144,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; - if (mShow) { - showDirectly(types); - } else { - hideDirectly(types); - } + mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE; mAnimator = ObjectAnimator.ofObject( controller, @@ -131,7 +162,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation onAnimationFinish(); } }); + mStartingAnimation = true; mAnimator.start(); + mStartingAnimation = false; } @Override @@ -185,6 +218,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private int mPendingTypesToShow; private int mLastLegacySoftInputMode; + private boolean mStartingAnimation; private SyncRtSurfaceTransactionApplier mApplier; @@ -266,6 +300,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** + * @see InsetsState#calculateVisibleInsets(Rect, Rect, int) + */ + public Rect calculateVisibleInsets(Rect legacyVisibleInsets, + @SoftInputModeFlags int softInputMode) { + return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode); + } + + /** * Called when the server has dispatched us a new set of inset controls. */ public void onControlsChanged(InsetsSourceControl[] activeControls) { @@ -312,7 +354,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Only one animator (with multiple InsetsType) can run at a time. // previous one should be cancelled for simplicity. cancelExistingAnimation(); - } else if (consumer.isVisible() + } else if (consumer.isRequestedVisible() && (mAnimationDirection == DIRECTION_NONE || mAnimationDirection == DIRECTION_HIDE)) { // no-op: already shown or animating in (because window visibility is @@ -338,7 +380,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); if (mAnimationDirection == DIRECTION_SHOW) { cancelExistingAnimation(); - } else if (!consumer.isVisible() + } else if (!consumer.isRequestedVisible() && (mAnimationDirection == DIRECTION_NONE || mAnimationDirection == DIRECTION_HIDE)) { // no-op: already hidden or animating out. @@ -363,12 +405,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener.onCancelled(); return; } - controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */); + controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */, + getLayoutInsetsDuringAnimationMode(types)); } private void controlAnimationUnchecked(@InsetsType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, - long durationMs, boolean fade) { + long durationMs, boolean fade, + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { if (types == 0) { // nothing to animate. return; @@ -398,7 +442,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls, - frame, mState, listener, typesReady, this, durationMs, fade); + frame, mState, listener, typesReady, this, durationMs, fade, + layoutInsetsDuringAnimation); mAnimationControls.add(controller); } @@ -412,7 +457,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean isReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - boolean setVisible = !consumer.isVisible(); + boolean setVisible = !consumer.isRequestedVisible(); if (setVisible) { // Show request switch(consumer.requestShow(fromIme)) { @@ -454,6 +499,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return typesReady; } + private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode( + @InsetsType int types) { + + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + + // Generally, we want to layout the opposite of the current state. This is to make animation + // callbacks easy to use: The can capture the layout values and then treat that as end-state + // during the animation. + // + // However, if controlling multiple sources, we want to treat it as shown if any of the + // types is currently hidden. + for (int i = internalTypes.size() - 1; i >= 0; i--) { + InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i)); + if (consumer == null) { + continue; + } + if (!consumer.isRequestedVisible()) { + return LAYOUT_INSETS_DURING_ANIMATION_SHOWN; + } + } + return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; + } + private void cancelExistingControllers(@InsetsType int types) { for (int i = mAnimationControls.size() - 1; i >= 0; i--) { InsetsAnimationControlImpl control = mAnimationControls.get(i); @@ -597,7 +665,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // and hidden state insets are correct. controlAnimationUnchecked( types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(), - true /* fade */); + true /* fade */, show + ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN + : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); } private void hideDirectly(@InsetsType int types) { @@ -629,18 +699,40 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override - public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) { - mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds); + public void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation, + AnimationBounds bounds, int layoutDuringAnimation) { + if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) { + showDirectly(types); + } else { + hideDirectly(types); + } + mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation); + mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this); + mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds); + listener.onReady(controller, types); + return true; + } + }); + mViewRoot.mView.invalidate(); } @VisibleForTesting public void dispatchAnimationFinished(InsetsAnimation animation) { - mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation); + mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation); } @VisibleForTesting @Override public void scheduleApplyChangeInsets() { + if (mStartingAnimation) { + mAnimCallback.run(); + mAnimCallbackScheduled = false; + return; + } if (!mAnimCallbackScheduled) { mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, mAnimCallback, null /* token*/); diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 324d562bfcfb..67ccfd6707f4 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; @@ -23,6 +24,7 @@ import android.os.Parcelable; import android.view.InsetsState.InternalInsetsType; import java.io.PrintWriter; +import java.util.Objects; /** * Represents the state of a single window generating insets for clients. @@ -34,6 +36,7 @@ public class InsetsSource implements Parcelable { /** Frame of the source in screen coordinate space */ private final Rect mFrame; + private @Nullable Rect mVisibleFrame; private boolean mVisible; private final Rect mTmpFrame = new Rect(); @@ -54,6 +57,10 @@ public class InsetsSource implements Parcelable { mFrame.set(frame); } + public void setVisibleFrame(@Nullable Rect visibleFrame) { + mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame; + } + public void setVisible(boolean visible) { mVisible = visible; } @@ -66,6 +73,10 @@ public class InsetsSource implements Parcelable { return mFrame; } + public @Nullable Rect getVisibleFrame() { + return mVisibleFrame; + } + public boolean isVisible() { return mVisible; } @@ -79,10 +90,22 @@ public class InsetsSource implements Parcelable { * source. */ public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { + return calculateInsets(relativeFrame, mFrame, ignoreVisibility); + } + + /** + * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets. + */ + public Insets calculateVisibleInsets(Rect relativeFrame) { + return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame, + false /* ignoreVisibility */); + } + + private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) { if (!ignoreVisibility && !mVisible) { return Insets.NONE; } - if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) { + if (!mTmpFrame.setIntersect(frame, relativeFrame)) { return Insets.NONE; } @@ -110,6 +133,9 @@ public class InsetsSource implements Parcelable { pw.print(prefix); pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType)); pw.print(" frame="); pw.print(mFrame.toShortString()); + if (mVisibleFrame != null) { + pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString()); + } pw.print(" visible="); pw.print(mVisible); pw.println(); } @@ -123,6 +149,7 @@ public class InsetsSource implements Parcelable { if (mType != that.mType) return false; if (mVisible != that.mVisible) return false; + if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; return mFrame.equals(that.mFrame); } @@ -137,6 +164,7 @@ public class InsetsSource implements Parcelable { public InsetsSource(Parcel in) { mType = in.readInt(); mFrame = in.readParcelable(null /* loader */); + mVisibleFrame = in.readParcelable(null /* loader */); mVisible = in.readBoolean(); } @@ -149,6 +177,7 @@ public class InsetsSource implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeParcelable(mFrame, 0 /* flags*/); + dest.writeParcelable(mVisibleFrame, 0 /* flags */); dest.writeBoolean(mVisible); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index c6d9898a425c..b2a5d915c2a6 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -53,7 +53,7 @@ public class InsetsSourceConsumer { } protected final InsetsController mController; - protected boolean mVisible; + protected boolean mRequestedVisible; private final Supplier<Transaction> mTransactionSupplier; private final @InternalInsetsType int mType; private final InsetsState mState; @@ -66,7 +66,7 @@ public class InsetsSourceConsumer { mState = state; mTransactionSupplier = transactionSupplier; mController = controller; - mVisible = InsetsState.getDefaultVisibility(type); + mRequestedVisible = InsetsState.getDefaultVisibility(type); } public void setControl(@Nullable InsetsSourceControl control) { @@ -94,12 +94,12 @@ public class InsetsSourceConsumer { @VisibleForTesting public void show() { - setVisible(true); + setRequestedVisible(true); } @VisibleForTesting public void hide() { - setVisible(false); + setRequestedVisible(false); } /** @@ -126,16 +126,16 @@ public class InsetsSourceConsumer { if (mSourceControl == null) { return false; } - if (mState.getSource(mType).isVisible() == mVisible) { + if (mState.getSource(mType).isVisible() == mRequestedVisible) { return false; } - mState.getSource(mType).setVisible(mVisible); + mState.getSource(mType).setVisible(mRequestedVisible); return true; } @VisibleForTesting - public boolean isVisible() { - return mVisible; + public boolean isRequestedVisible() { + return mRequestedVisible; } /** @@ -157,11 +157,15 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } - private void setVisible(boolean visible) { - if (mVisible == visible) { + /** + * Sets requested visibility from the client, regardless of whether we are able to control it at + * the moment. + */ + private void setRequestedVisible(boolean requestedVisible) { + if (mRequestedVisible == requestedVisible) { return; } - mVisible = visible; + mRequestedVisible = requestedVisible; applyLocalVisibilityOverride(); mController.notifyVisibilityChanged(); } @@ -173,7 +177,7 @@ public class InsetsSourceConsumer { } final Transaction t = mTransactionSupplier.get(); - if (mVisible) { + if (mRequestedVisible) { t.show(mSourceControl.getLeash()); } else { t.hide(mSourceControl.getLeash()); diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index ae1e579da8f6..e33ca70c222e 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -19,12 +19,14 @@ package android.view; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.ViewRootImpl.sNewInsetsMode; import static android.view.WindowInsets.Type.IME; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.indexOf; +import static android.view.WindowInsets.Type.isVisibleInsetsType; import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; @@ -41,6 +43,7 @@ import android.util.SparseIntArray; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -186,6 +189,32 @@ public class InsetsState implements Parcelable { : systemBars()); } + public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets, + @SoftInputModeFlags int softInputMode) { + if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { + return legacyVisibleInsets; + } + + Insets insets = Insets.NONE; + for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { + InsetsSource source = mSources.get(type); + if (source == null) { + continue; + } + if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) { + continue; + } + + // Ignore everything that's not a system bar or IME. + int publicType = InsetsState.toPublicType(type); + if (!isVisibleInsetsType(publicType, softInputMode)) { + continue; + } + insets = Insets.max(source.calculateVisibleInsets(frame), insets); + } + return insets.toRect(); + } + private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap, @Nullable boolean[] typeVisibilityMap) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0db80e2749c3..13d609b16541 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11117,7 +11117,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)} + * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)} + * when Window Insets animation is being prepared. + * @param animation current animation + * + * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation) + */ + public void dispatchWindowInsetsAnimationPrepare( + @NonNull InsetsAnimation animation) { + if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { + mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation); + } + } + + /** + * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)} * when Window Insets animation is started. * @param animation current animation * @param bounds the upper and lower {@link AnimationBounds} that provides range of @@ -11125,10 +11139,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return the upper and lower {@link AnimationBounds}. */ @NonNull - public AnimationBounds dispatchWindowInsetsAnimationStarted( + public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { - return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds); + return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds); } return bounds; } @@ -11149,13 +11163,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)} + * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)} * when Window Insets animation finishes. * @param animation The current ongoing {@link InsetsAnimation}. */ - public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) { + public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { - mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation); + mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 5fb71773db8f..047d7da7536f 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7199,13 +7199,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + public void dispatchWindowInsetsAnimationPrepare( + @NonNull InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationPrepare(animation); + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation); + } + } + + @Override @NonNull - public AnimationBounds dispatchWindowInsetsAnimationStarted( + public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { - super.dispatchWindowInsetsAnimationStarted(animation, bounds); + super.dispatchWindowInsetsAnimationStart(animation, bounds); final int count = getChildCount(); for (int i = 0; i < count; i++) { - getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds); + getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds); } return bounds; } @@ -7222,11 +7232,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) { - super.dispatchWindowInsetsAnimationFinished(animation); + public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationFinish(animation); final int count = getChildCount(); for (int i = 0; i < count; i++) { - getChildAt(i).dispatchWindowInsetsAnimationFinished(animation); + getChildAt(i).dispatchWindowInsetsAnimationFinish(animation); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index bf8dc65abe28..ca8ba4ca1b8a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1464,6 +1464,7 @@ public final class ViewRootImpl implements ViewParent, return; } mApplyInsetsRequested = true; + requestLayout(); // If this changes during traversal, no need to schedule another one as it will dispatch it // during the current traversal. @@ -2102,6 +2103,12 @@ public final class ViewRootImpl implements ViewParent, Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + private void updateVisibleInsets() { + Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets, + mWindowAttributes.softInputMode); + mAttachInfo.mVisibleInsets.set(visibleInsets); + } + InsetsController getInsetsController() { return mInsetsController; } @@ -2250,7 +2257,7 @@ public final class ViewRootImpl implements ViewParent, insetsChanged = true; } if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { - mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + updateVisibleInsets(); if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } @@ -2316,6 +2323,7 @@ public final class ViewRootImpl implements ViewParent, if (mApplyInsetsRequested) { mApplyInsetsRequested = false; + updateVisibleInsets(); dispatchApplyInsets(host); if (mLayoutRequested) { // Short-circuit catching a new layout request here, so @@ -2500,7 +2508,7 @@ public final class ViewRootImpl implements ViewParent, contentInsetsChanged = true; } if (visibleInsetsChanged) { - mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + updateVisibleInsets(); if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java index 607a87047733..253794f70ef2 100644 --- a/core/java/android/view/WindowContainerTransaction.java +++ b/core/java/android/view/WindowContainerTransaction.java @@ -62,6 +62,18 @@ public class WindowContainerTransaction implements Parcelable { return this; } + /** + * Notify activies within the hiearchy of a container that they have entered picture-in-picture + * mode with the given bounds. + */ + public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container, + Rect bounds) { + Change chg = getOrCreateChange(container.asBinder()); + chg.mSchedulePipCallback = true; + chg.mPinnedBounds = new Rect(bounds); + return this; + } + public Map<IBinder, Change> getChanges() { return mChanges; } @@ -104,12 +116,20 @@ public class WindowContainerTransaction implements Parcelable { private @ActivityInfo.Config int mConfigSetMask = 0; private @WindowConfiguration.WindowConfig int mWindowSetMask = 0; + private boolean mSchedulePipCallback = false; + private Rect mPinnedBounds = null; + public Change() {} protected Change(Parcel in) { mConfiguration.readFromParcel(in); mConfigSetMask = in.readInt(); mWindowSetMask = in.readInt(); + mSchedulePipCallback = (in.readInt() != 0); + if (mSchedulePipCallback ) { + mPinnedBounds = new Rect(); + mPinnedBounds.readFromParcel(in); + } } public Configuration getConfiguration() { @@ -126,6 +146,14 @@ public class WindowContainerTransaction implements Parcelable { return mWindowSetMask; } + /** + * Returns the bounds to be used for scheduling the enter pip callback + * or null if no callback is to be scheduled. + */ + public Rect getEnterPipBounds() { + return mPinnedBounds; + } + @Override public String toString() { final boolean changesBounds = @@ -151,6 +179,11 @@ public class WindowContainerTransaction implements Parcelable { mConfiguration.writeToParcel(dest, flags); dest.writeInt(mConfigSetMask); dest.writeInt(mWindowSetMask); + + dest.writeInt(mSchedulePipCallback ? 1 : 0); + if (mSchedulePipCallback ) { + mPinnedBounds.writeToParcel(dest, flags); + } } @Override diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 9df131de6754..9291b5652425 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -29,6 +29,8 @@ import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT; import static android.view.WindowInsets.Type.all; import static android.view.WindowInsets.Type.indexOf; import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import android.annotation.IntDef; import android.annotation.IntRange; @@ -40,6 +42,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethod; @@ -1289,6 +1292,17 @@ public final class WindowInsets { public static @InsetsType int all() { return 0xFFFFFFFF; } + + /** + * Checks whether the specified type is considered to be part of visible insets. + * @hide + */ + public static boolean isVisibleInsetsType(int type, + @SoftInputModeFlags int softInputModeFlags) { + int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST; + return (type & Type.systemBars()) != 0 + || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0); + } } /** diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index 5e71f271f1d4..e84c3e33c000 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -20,6 +20,7 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Insets; +import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.animation.Interpolator; @@ -30,7 +31,47 @@ import android.view.animation.Interpolator; public interface WindowInsetsAnimationCallback { /** - * Called when an inset animation gets started. + * Called when an insets animation is about to start and before the views have been laid out in + * the end state of the animation. The ordering of events during an insets animation is the + * following: + * <p> + * <ul> + * <li>Application calls {@link WindowInsetsController#hideInputMethod()}, + * {@link WindowInsetsController#showInputMethod()}, + * {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li> + * <li>onPrepare is called on the view hierarchy listeners</li> + * <li>{@link View#onApplyWindowInsets} will be called with the end state of the + * animation</li> + * <li>View hierarchy gets laid out according to the changes the application has requested + * due to the new insets being dispatched</li> + * <li>{@link #onStart} is called <em>before</em> the view + * hierarchy gets drawn in the new laid out state</li> + * <li>{@link #onProgress} is called immediately after with the animation start state</li> + * <li>The frame gets drawn.</li> + * </ul> + * <p> + * This ordering allows the application to inspect the end state after the animation has + * finished, and then revert to the starting state of the animation in the first + * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and + * related methods. + * <p> + * Note: If the animation is application controlled by using + * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation + * is undefined as the application may decide on the end state only by passing in the + * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this + * situation, the system will dispatch the insets in the opposite visibility state before the + * animation starts. Example: When controlling the input method with + * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently + * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for + * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}. + * + * @param animation The animation that is about to start. + */ + default void onPrepare(@NonNull InsetsAnimation animation) { + } + + /** + * Called when an insets animation gets started. * <p> * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical: * It will starts at the root of the view hierarchy and then traverse it and invoke the callback @@ -45,7 +86,7 @@ public interface WindowInsetsAnimationCallback { * subtree of the hierarchy. */ @NonNull - default AnimationBounds onStarted( + default AnimationBounds onStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { return bounds; } @@ -72,12 +113,12 @@ public interface WindowInsetsAnimationCallback { WindowInsets onProgress(@NonNull WindowInsets insets); /** - * Called when an inset animation has finished. + * Called when an insets animation has finished. * * @param animation The animation that has finished running. This will be the same instance as - * passed into {@link #onStarted} + * passed into {@link #onStart} */ - default void onFinished(@NonNull InsetsAnimation animation) { + default void onFinish(@NonNull InsetsAnimation animation) { } /** @@ -253,14 +294,14 @@ public interface WindowInsetsAnimationCallback { /** * Insets both the lower and upper bound by the specified insets. This is to be used in - * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has + * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has * been used to offset or clip its children, and the children shouldn't worry about that * part anymore. * * @param insets The amount to inset. * @return A copy of this instance inset in the given directions. * @see WindowInsets#inset - * @see WindowInsetsAnimationCallback#onStarted + * @see WindowInsetsAnimationCallback#onStart */ @NonNull public AnimationBounds inset(@NonNull Insets insets) { diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java index 8a226c1bbe23..f91254de33ff 100644 --- a/core/java/android/view/WindowInsetsAnimationControlListener.java +++ b/core/java/android/view/WindowInsetsAnimationControlListener.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Hide; import android.annotation.NonNull; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.EditorInfo; @@ -26,6 +27,12 @@ import android.view.inputmethod.EditorInfo; public interface WindowInsetsAnimationControlListener { /** + * @hide + */ + default void onPrepare(int types) { + } + + /** * Called when the animation is ready to be controlled. This may be delayed when the IME needs * to redraw because of an {@link EditorInfo} change, or when the window is starting up. * diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 6de56be2f3c5..9d7f292dbdf5 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -149,7 +149,8 @@ public interface WindowInsetsController { * * @param types The {@link InsetsType}s the application has requested to control. * @param durationMillis duration of animation in - * {@link java.util.concurrent.TimeUnit#MILLISECONDS} + * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the + * animation doesn't have a predetermined duration. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * windows are ready to be controlled, among other callbacks. * @hide @@ -162,7 +163,8 @@ public interface WindowInsetsController { * modifying the position of the IME when it's causing insets. * * @param durationMillis duration of the animation in - * {@link java.util.concurrent.TimeUnit#MILLISECONDS} + * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the + * animation doesn't have a predetermined duration. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * IME are ready to be controlled, among other callbacks. */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 67ce8d2e49b5..f3007a794344 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -93,9 +93,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** @@ -429,7 +432,10 @@ public final class InputMethodManager { * in a background thread. Later, if there is an actual startInput it will wait on * main thread till the background thread completes. */ - private CompletableFuture<Void> mWindowFocusGainFuture; + private Future<?> mWindowFocusGainFuture; + + private ExecutorService mStartInputWorker = Executors.newSingleThreadExecutor( + new ImeThreadFactory("StartInputWorker")); /** * The instance that has previously been sent to the input method. @@ -790,6 +796,19 @@ public final class InputMethodManager { } } + private static class ImeThreadFactory implements ThreadFactory { + private final String mThreadName; + + ImeThreadFactory(String name) { + mThreadName = name; + } + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, mThreadName); + } + } + final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { @@ -1978,7 +1997,7 @@ public final class InputMethodManager { if (mWindowFocusGainFuture != null) { mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */); } - mWindowFocusGainFuture = CompletableFuture.runAsync(() -> { + mWindowFocusGainFuture = mStartInputWorker.submit(() -> { if (checkFocusNoStartInput(forceNewFocus1)) { // We need to restart input on the current focus view. This // should be done in conjunction with telling the system service diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index a0cf53437a50..20af76b0d5ca 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -5921,8 +5921,6 @@ public class Editor { // The offsets of that last touch down event. Remembered to start selection there. private int mMinTouchOffset, mMaxTouchOffset; - private boolean mGestureStayedInTapRegion; - // Where the user first starts the drag motion. private int mStartOffset = -1; @@ -6029,8 +6027,7 @@ public class Editor { eventX, eventY); // Double tap detection - if (mGestureStayedInTapRegion - && mTouchState.isMultiTapInSameArea() + if (mTouchState.isMultiTapInSameArea() && (isMouse || isPositionOnText(eventX, eventY))) { if (TextView.DEBUG_CURSOR) { logCursor("SelectionModifierCursorController: onTouchEvent", @@ -6043,7 +6040,6 @@ public class Editor { } mDiscardNextActionUp = true; } - mGestureStayedInTapRegion = true; mHaventMovedEnoughToStartDrag = true; } break; @@ -6059,25 +6055,8 @@ public class Editor { break; case MotionEvent.ACTION_MOVE: - final ViewConfiguration viewConfig = ViewConfiguration.get( - mTextView.getContext()); - - if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) { - final float deltaX = eventX - mTouchState.getLastDownX(); - final float deltaY = eventY - mTouchState.getLastDownY(); - final float distanceSquared = deltaX * deltaX + deltaY * deltaY; - - if (mGestureStayedInTapRegion) { - int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop(); - mGestureStayedInTapRegion = - distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop; - } - if (mHaventMovedEnoughToStartDrag) { - // We don't start dragging until the user has moved enough. - int touchSlop = viewConfig.getScaledTouchSlop(); - mHaventMovedEnoughToStartDrag = - distanceSquared <= touchSlop * touchSlop; - } + if (mHaventMovedEnoughToStartDrag) { + mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag(); } if (isMouse && !isDragAcceleratorActive()) { diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java index d53099d44f6f..6277afe2f613 100644 --- a/core/java/android/widget/EditorTouchState.java +++ b/core/java/android/widget/EditorTouchState.java @@ -31,13 +31,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Helper class used by {@link Editor} to track state for touch events. + * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here + * should be replaced with {@link android.view.GestureDetector}. * * @hide */ @VisibleForTesting(visibility = PACKAGE) public class EditorTouchState { private float mLastDownX, mLastDownY; + private long mLastDownMillis; private float mLastUpX, mLastUpY; private long mLastUpMillis; @@ -106,9 +108,18 @@ public class EditorTouchState { final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); + + // We check both the time between the last up and current down event, as well as the + // time between the first down and up events. The latter check is necessary to handle + // the case when the user taps, drags/holds for some time, and then lifts up and + // quickly taps in the same area. This scenario should not be treated as a double-tap. + // This follows the behavior in GestureDetector. final long millisSinceLastUp = event.getEventTime() - mLastUpMillis; + final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis; + // Detect double tap and triple click. if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout() + && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout() && (mMultiTapStatus == MultiTapStatus.FIRST_TAP || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) { if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) { @@ -133,6 +144,7 @@ public class EditorTouchState { } mLastDownX = event.getX(); mLastDownY = event.getY(); + mLastDownMillis = event.getEventTime(); mMovedEnoughForDrag = false; mIsDragCloseToVertical = false; } else if (action == MotionEvent.ACTION_UP) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index d86766ebdf58..01a0e9b15463 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -565,7 +565,8 @@ public class RemoteViews implements Parcelable, Filter { } private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { - if (icon != null && icon.getType() == Icon.TYPE_URI) { + if (icon != null && (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { visitor.accept(icon.getUri()); } } diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java index 0a0d05ce8fc8..de204badfd0d 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -16,6 +16,7 @@ package com.android.internal.app; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; +import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; import static android.view.accessibility.AccessibilityManager.ShortcutType; import android.accessibilityservice.AccessibilityServiceInfo; @@ -24,13 +25,17 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -49,7 +54,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.StringJoiner; /** * Activity used to display and persist a service or feature target for the Accessibility button. @@ -59,12 +67,46 @@ public class AccessibilityButtonChooserActivity extends Activity { private static final String MAGNIFICATION_COMPONENT_ID = "com.android.server.accessibility.MagnificationController"; + private static final char SERVICES_SEPARATOR = ':'; + private static final TextUtils.SimpleStringSplitter sStringColonSplitter = + new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); + private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType( + ACCESSIBILITY_BUTTON); + private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType( + ACCESSIBILITY_SHORTCUT_KEY); + private int mShortcutType; private List<AccessibilityButtonTarget> mTargets = new ArrayList<>(); private AlertDialog mAlertDialog; private TargetAdapter mTargetAdapter; /** + * Annotation for different user shortcut type UI type. + * + * {@code DEFAULT} for displaying default value. + * {@code SOFTWARE} for displaying specifying the accessibility services or features which + * choose accessibility button in the navigation bar as preferred shortcut. + * {@code HARDWARE} for displaying specifying the accessibility services or features which + * choose accessibility shortcut as preferred shortcut. + * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly + * tapping screen 3 times as preferred shortcut. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + UserShortcutType.DEFAULT, + UserShortcutType.SOFTWARE, + UserShortcutType.HARDWARE, + UserShortcutType.TRIPLETAP, + }) + /** Denotes the user shortcut type. */ + public @interface UserShortcutType { + int DEFAULT = 0; + int SOFTWARE = 1; // 1 << 0 + int HARDWARE = 2; // 1 << 1 + int TRIPLETAP = 4; // 1 << 2 + } + + /** * Annotation for different accessibilityService fragment UI type. * * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service @@ -395,19 +437,63 @@ public class AccessibilityButtonChooserActivity extends Activity { } private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) { - // TODO(b/146967898): disable service when deleting the target and the target only have - // last one shortcut item, only remove it from shortcut list otherwise. - if ((mShortcutType == ACCESSIBILITY_BUTTON) && (mTargets.get(position).mFragmentType - != AccessibilityServiceFragmentType.LEGACY)) { - mTargets.remove(position); - mTargetAdapter.notifyDataSetChanged(); + final AccessibilityButtonTarget target = mTargets.get(position); + final ComponentName targetComponentName = + ComponentName.unflattenFromString(target.getId()); + + switch (target.getFragmentType()) { + case AccessibilityServiceFragmentType.INVISIBLE: + onInvisibleTargetDeleted(targetComponentName); + break; + case AccessibilityServiceFragmentType.INTUITIVE: + onIntuitiveTargetDeleted(targetComponentName); + break; + case AccessibilityServiceFragmentType.LEGACY: + case AccessibilityServiceFragmentType.BOUNCE: + // Do nothing + break; + default: + throw new IllegalStateException("Unexpected fragment type"); } + mTargets.remove(position); + mTargetAdapter.notifyDataSetChanged(); + if (mTargets.isEmpty()) { mAlertDialog.dismiss(); } } + private void onInvisibleTargetDeleted(ComponentName componentName) { + if (mShortcutType == ACCESSIBILITY_BUTTON) { + optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); + + if (!hasValueInSettings(this, + ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) { + setAccessibilityServiceState(this, componentName, /* enabled= */ false); + } + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); + + if (!hasValueInSettings(this, + ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) { + setAccessibilityServiceState(this, componentName, /* enabled= */ false); + } + } else { + throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); + } + } + + private void onIntuitiveTargetDeleted(ComponentName componentName) { + if (mShortcutType == ACCESSIBILITY_BUTTON) { + optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); + } else { + throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); + } + } + private void onCancelButtonClicked() { mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); mTargetAdapter.notifyDataSetChanged(); @@ -437,4 +523,166 @@ public class AccessibilityButtonChooserActivity extends Activity { mAlertDialog.getListView().setOnItemClickListener( isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected); } + + /** + * @return the set of enabled accessibility services for {@param userId}. If there are no + * services, it returns the unmodifiable {@link Collections#emptySet()}. + */ + private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) { + final String enabledServicesSetting = Settings.Secure.getStringForUser( + context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userId); + if (TextUtils.isEmpty(enabledServicesSetting)) { + return Collections.emptySet(); + } + + final Set<ComponentName> enabledServices = new HashSet<>(); + final TextUtils.StringSplitter colonSplitter = + new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); + colonSplitter.setString(enabledServicesSetting); + + for (String componentNameString : colonSplitter) { + final ComponentName enabledService = ComponentName.unflattenFromString( + componentNameString); + if (enabledService != null) { + enabledServices.add(enabledService); + } + } + + return enabledServices; + } + + /** + * Changes an accessibility component's state. + */ + private void setAccessibilityServiceState(Context context, ComponentName componentName, + boolean enabled) { + setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId()); + } + + /** + * Changes an accessibility component's state for {@param userId}. + */ + private void setAccessibilityServiceState(Context context, ComponentName componentName, + boolean enabled, int userId) { + Set<ComponentName> enabledServices = getEnabledServicesFromSettings( + context, userId); + + if (enabledServices.isEmpty()) { + enabledServices = new ArraySet<>(/* capacity= */ 1); + } + + if (enabled) { + enabledServices.add(componentName); + } else { + enabledServices.remove(componentName); + } + + final StringBuilder enabledServicesBuilder = new StringBuilder(); + for (ComponentName enabledService : enabledServices) { + enabledServicesBuilder.append(enabledService.flattenToString()); + enabledServicesBuilder.append( + SERVICES_SEPARATOR); + } + + final int enabledServicesBuilderLength = enabledServicesBuilder.length(); + if (enabledServicesBuilderLength > 0) { + enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); + } + + Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + enabledServicesBuilder.toString(), userId); + } + + /** + * Opts out component name into colon-separated {@code shortcutType} key's string in Settings. + * + * @param context The current context. + * @param shortcutType The preferred shortcut type user selected. + * @param componentName The component name that need to be opted out from Settings. + */ + private void optOutValueFromSettings( + Context context, int shortcutType, ComponentName componentName) { + final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR)); + final String targetsKey = convertToKey(shortcutType); + final String targetsValue = Settings.Secure.getString(context.getContentResolver(), + targetsKey); + + if (TextUtils.isEmpty(targetsValue)) { + return; + } + + sStringColonSplitter.setString(targetsValue); + while (sStringColonSplitter.hasNext()) { + final String name = sStringColonSplitter.next(); + if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) { + continue; + } + joiner.add(name); + } + + Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString()); + } + + /** + * Returns if component name existed in Settings. + * + * @param context The current context. + * @param shortcutType The preferred shortcut type user selected. + * @param componentName The component name that need to be checked existed in Settings. + * @return {@code true} if componentName existed in Settings. + */ + private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType, + @NonNull ComponentName componentName) { + final String targetKey = convertToKey(shortcutType); + final String targetString = Settings.Secure.getString(context.getContentResolver(), + targetKey); + + if (TextUtils.isEmpty(targetString)) { + return false; + } + + sStringColonSplitter.setString(targetString); + while (sStringColonSplitter.hasNext()) { + final String name = sStringColonSplitter.next(); + if ((componentName.flattenToString()).equals(name)) { + return true; + } + } + + return false; + } + + /** + * Converts {@link UserShortcutType} to key in Settings. + * + * @param type The shortcut type. + * @return Mapping key in Settings. + */ + private String convertToKey(@UserShortcutType int type) { + switch (type) { + case UserShortcutType.SOFTWARE: + return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT; + case UserShortcutType.HARDWARE: + return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; + case UserShortcutType.TRIPLETAP: + return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED; + default: + throw new IllegalArgumentException( + "Unsupported user shortcut type: " + type); + } + } + + private static @UserShortcutType int convertToUserType(@ShortcutType int type) { + switch (type) { + case ACCESSIBILITY_BUTTON: + return UserShortcutType.SOFTWARE; + case ACCESSIBILITY_SHORTCUT_KEY: + return UserShortcutType.HARDWARE; + default: + throw new IllegalArgumentException( + "Unsupported shortcut type:" + type); + } + } } diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl index d94294f0aa22..74bfb963c6b0 100644 --- a/core/java/com/android/internal/app/ISoundTriggerService.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl @@ -61,10 +61,6 @@ interface ISoundTriggerService { int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam, int value); - /** - * @throws UnsupportedOperationException if hal or model do not support this API. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - */ int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam); @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId, diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index f462f5d2571d..be2d1d60e9a2 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractionSessionListener; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -91,6 +92,49 @@ interface IVoiceInteractionManagerService { */ int stopRecognition(in IVoiceInteractionService service, int keyphraseId, in IRecognitionStatusCallback callback); + /** + * Set a model specific ModelParams with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * queryParameter should be checked first before calling this method. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + int setParameter(in IVoiceInteractionService service, int keyphraseId, + in ModelParams modelParam, int value); + /** + * Get a model specific ModelParams. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See ModelParams for parameter default values. + * queryParameter should be checked first before calling this method. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return value of parameter + */ + int getParameter(in IVoiceInteractionService service, int keyphraseId, + in ModelParams modelParam); + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling setParameter or getParameter. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return supported range of parameter, null if not supported + */ + @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service, + int keyphraseId, in ModelParams modelParam); /** * @return the component name for the currently active voice interaction service diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index cca97f689f4e..3a450de6c8e6 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -16,6 +16,9 @@ package com.android.internal.logging; +import android.annotation.NonNull; +import android.annotation.Nullable; + /** * Logging interface for UI events. Normal implementation is UiEventLoggerImpl. * For testing, use fake implementation UiEventLoggerFake. @@ -26,13 +29,24 @@ public interface UiEventLogger { /** Put your Event IDs in enums that implement this interface, and document them using the * UiEventEnum annotation. * Event IDs must be globally unique. This will be enforced by tooling (forthcoming). - * OEMs should use event IDs above 100000. + * OEMs should use event IDs above 100000 and below 1000000 (1 million). */ interface UiEventEnum { int getId(); } + + /** + * Log a simple event, with no package information. Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + */ + void log(@NonNull UiEventEnum event); + /** - * Log a simple event, with no package or instance ID. + * Log an event with package information. Does nothing if event.getId() <= 0. + * Give both uid and packageName if both are known, but one may be omitted if unknown. + * @param event an enum implementing UiEventEnum interface. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). */ - void log(UiEventEnum eventID); + void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index e64fba2d2a31..bdf460c710c3 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -24,14 +24,16 @@ import android.util.StatsLog; * See UiEventReported atom in atoms.proto for more context. */ public class UiEventLoggerImpl implements UiEventLogger { - /** - * Log a simple event, with no package or instance ID. - */ @Override public void log(UiEventEnum event) { + log(event, 0, null); + } + + @Override + public void log(UiEventEnum event, int uid, String packageName) { final int eventID = event.getId(); if (eventID > 0) { - StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null); + StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName); } } } diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 92e9bbb77bd3..6be5b81afee2 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -30,7 +30,7 @@ public class UiEventLoggerFake implements UiEventLogger { /** * Immutable data class used to record fake log events. */ - public class FakeUiEvent { + public static class FakeUiEvent { public final int eventId; public final int uid; public final String packageName; @@ -44,15 +44,20 @@ public class UiEventLoggerFake implements UiEventLogger { private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>(); + public Queue<FakeUiEvent> getLogs() { + return mLogs; + } + @Override public void log(UiEventEnum event) { + log(event, 0, null); + } + + @Override + public void log(UiEventEnum event, int uid, String packageName) { final int eventId = event.getId(); if (eventId > 0) { - mLogs.offer(new FakeUiEvent(eventId, 0, null)); + mLogs.offer(new FakeUiEvent(eventId, uid, packageName)); } } - - public Queue<FakeUiEvent> getLogs() { - return mLogs; - } } diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 5bc96d8ee1d3..408a7a8e139a 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -126,7 +126,9 @@ public class Preconditions { * @param reference an object reference * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null + * @deprecated - use {@link java.util.Objects.requireNonNull} instead. */ + @Deprecated @UnsupportedAppUsage public static @NonNull <T> T checkNotNull(final T reference) { if (reference == null) { @@ -144,7 +146,9 @@ public class Preconditions { * be converted to a string using {@link String#valueOf(Object)} * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null + * @deprecated - use {@link java.util.Objects.requireNonNull} instead. */ + @Deprecated @UnsupportedAppUsage public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) { if (reference == null) { @@ -154,26 +158,6 @@ public class Preconditions { } /** - * Ensures that an object reference passed as a parameter to the calling - * method is not null. - * - * @param reference an object reference - * @param messageTemplate a printf-style message template to use if the check fails; will - * be converted to a string using {@link String#format(String, Object...)} - * @param messageArgs arguments for {@code messageTemplate} - * @return the non-null reference that was validated - * @throws NullPointerException if {@code reference} is null - */ - public static @NonNull <T> T checkNotNull(final T reference, - final String messageTemplate, - final Object... messageArgs) { - if (reference == null) { - throw new NullPointerException(String.format(messageTemplate, messageArgs)); - } - return reference; - } - - /** * Ensures the truth of an expression involving the state of the calling * instance, but not involving any parameters to the calling method. * diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index 557075cc5bfa..2de5b7fd45f6 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -32,6 +32,10 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } option (android.msg_privacy).dest = DEST_AUTOMATIC; // The package name of the app @@ -54,6 +58,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a808ed8fcc81..d887032dbdbc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -370,6 +370,7 @@ <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" /> <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" /> <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" /> + <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" /> @@ -933,11 +934,10 @@ <!-- Allows an application a broad access to external storage in scoped storage. Intended to be used by few apps that need to manage files on behalf of the users. - <p>Protection level: signature|appop - <p>This protection level is temporary and will most likely be changed to |preinstalled --> + <p>Protection level: signature|appop|preinstalled --> <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:permissionGroup="android.permission-group.UNDEFINED" - android:protectionLevel="signature|appop" /> + android:protectionLevel="signature|appop|preinstalled" /> <!-- ====================================================================== --> <!-- Permissions for accessing the device location --> @@ -1655,6 +1655,7 @@ <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and location permissions. <p>Not for use by third-party or privileged applications. + @SystemApi @hide --> <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION" diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java index 68b9b0079761..f4709ff0bc00 100644 --- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java @@ -33,14 +33,15 @@ import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.support.test.uiautomator.UiDevice; import android.test.InstrumentationTestCase; import android.util.Log; -import libcore.io.Streams; - import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; +import libcore.io.Streams; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -63,6 +64,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { private static final String TAG = "DownloadManagerBaseTest"; protected DownloadManager mDownloadManager = null; private MockWebServer mServer = null; + private UiDevice mUiDevice = null; protected String mFileType = "text/plain"; protected Context mContext = null; protected MultipleDownloadsCompletedReceiver mReceiver = null; @@ -234,6 +236,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { @Override public void setUp() throws Exception { mContext = getInstrumentation().getContext(); + mUiDevice = UiDevice.getInstance(getInstrumentation()); mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); mServer = new MockWebServer(); mServer.play(); @@ -512,7 +515,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { Log.i(LOG_TAG, "Setting WiFi State to: " + enable); WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); - manager.setWifiEnabled(enable); + mUiDevice.executeShellCommand("svc wifi " + (enable ? "enable" : "disable")); String timeoutMessage = "Timed out waiting for Wifi to be " + (enable ? "enabled!" : "disabled!"); diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java index c6c0b46d0505..b13bcd1311f6 100644 --- a/core/tests/coretests/src/android/net/NetworkKeyTest.java +++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java @@ -18,6 +18,7 @@ package android.net; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.net.wifi.ScanResult; @@ -107,7 +108,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_nullSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.BSSID = VALID_BSSID; assertNull(NetworkKey.createFromScanResult(scanResult)); @@ -115,7 +116,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_emptySsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = ""; scanResult.BSSID = VALID_BSSID; @@ -124,7 +125,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_noneSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = WifiManager.UNKNOWN_SSID; scanResult.BSSID = VALID_BSSID; @@ -133,7 +134,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_nullBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; assertNull(NetworkKey.createFromScanResult(scanResult)); @@ -141,7 +142,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_emptyBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = ""; @@ -150,7 +151,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_invalidBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = INVALID_BSSID; @@ -159,7 +160,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_validSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = VALID_BSSID; diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 0e19ca84d433..d0fd92a838c9 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -90,12 +90,12 @@ public class ImeInsetsSourceConsumerTest { mImeConsumer.onWindowFocusGained(); mImeConsumer.applyImeVisibility(true); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test if setVisibility can hide IME mImeConsumer.applyImeVisibility(false); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); } diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 179929f2aae0..fa61a0a0250b 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -16,11 +16,11 @@ package android.view; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowInsets.Type.systemBars; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -39,8 +39,6 @@ import android.view.SurfaceControl.Transaction; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.test.InsetsModeSession; -import androidx.test.runner.AndroidJUnit4; - import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -52,6 +50,8 @@ import org.mockito.MockitoAnnotations; import java.util.List; +import androidx.test.runner.AndroidJUnit4; + /** * Tests for {@link InsetsAnimationControlImpl}. * @@ -116,7 +116,7 @@ public class InsetsAnimationControlImplTest { mController = new InsetsAnimationControlImpl(controls, new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), mMockController, 10 /* durationMs */, - false /* fade */); + false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN); } @Test diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index a89fc1e6315f..1db96b15f83a 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -68,6 +68,7 @@ public class InsetsControllerTest { private InsetsController mController; private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; + private ViewRootImpl mViewRoot; @Before public void setup() { @@ -77,13 +78,13 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { Context context = InstrumentationRegistry.getTargetContext(); // cannot mock ViewRootImpl since it's final. - ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); + mViewRoot = new ViewRootImpl(context, context.getDisplay()); try { - viewRootImpl.setView(new TextView(context), new LayoutParams(), null); + mViewRoot.setView(new TextView(context), new LayoutParams(), null); } catch (BadTokenException e) { // activity isn't running, we will ignore BadTokenException. } - mController = new InsetsController(viewRootImpl); + mController = new InsetsController(mViewRoot); final Rect rect = new Rect(5, 5, 5, 5); mController.calculateInsets( false, @@ -117,16 +118,22 @@ public class InsetsControllerTest { @Test public void testControlsRevoked_duringAnim() { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + InsetsSourceControl control = + new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); + mController.onControlsChanged(new InsetsSourceControl[] { control }); - WindowInsetsAnimationControlListener mockListener = - mock(WindowInsetsAnimationControlListener.class); - mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener); - verify(mockListener).onReady(any(), anyInt()); - mController.onControlsChanged(new InsetsSourceControl[0]); - verify(mockListener).onCancelled(); + WindowInsetsAnimationControlListener mockListener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, + mockListener); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(mockListener).onReady(any(), anyInt()); + mController.onControlsChanged(new InsetsSourceControl[0]); + verify(mockListener).onCancelled(); + }); } @Test @@ -154,16 +161,16 @@ public class InsetsControllerTest { mController.show(Type.all()); // quickly jump to final state by cancelling it. mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false /* setVisible */); mController.hide(Type.all()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -180,10 +187,10 @@ public class InsetsControllerTest { mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(); mController.applyImeVisibility(true); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -201,16 +208,16 @@ public class InsetsControllerTest { // test show select types. mController.show(types); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all mController.hide(types); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -227,29 +234,29 @@ public class InsetsControllerTest { // test show select types. mController.show(types); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all mController.hide(Type.all()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test single show mController.show(Type.navigationBars()); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test single hide mController.hide(Type.navigationBars()); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -267,31 +274,31 @@ public class InsetsControllerTest { mController.show(Type.navigationBars()); mController.show(Type.systemBars()); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.navigationBars()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); int types = Type.navigationBars() | Type.systemBars(); // show two at a time and hide one by one. mController.show(types); mController.hide(Type.navigationBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -309,15 +316,15 @@ public class InsetsControllerTest { mController.show(types); mController.hide(Type.navigationBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -336,12 +343,16 @@ public class InsetsControllerTest { ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor = ArgumentCaptor.forClass(WindowInsetsAnimationController.class); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(mockListener).onReady(controllerCaptor.capture(), anyInt()); controllerCaptor.getValue().finish(false /* shown */); }); waitUntilNextFrame(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible()); + assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 7af833bfcba4..492c03653990 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -96,7 +96,7 @@ public class InsetsSourceConsumerTest { @Test public void testHide() { mConsumer.hide(); - assertFalse("Consumer should not be visible", mConsumer.isVisible()); + assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible()); verify(mSpyInsetsSource).setVisible(eq(false)); } @@ -106,7 +106,7 @@ public class InsetsSourceConsumerTest { // Insets source starts out visible mConsumer.hide(); mConsumer.show(); - assertTrue("Consumer should be visible", mConsumer.isVisible()); + assertTrue("Consumer should be visible", mConsumer.isRequestedVisible()); verify(mSpyInsetsSource).setVisible(eq(false)); verify(mSpyInsetsSource).setVisible(eq(true)); } diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java index d7f50bafdc0a..e3b08bb14e46 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -108,5 +108,30 @@ public class InsetsSourceTest { assertEquals(Insets.of(0, 100, 0, 0), insets); } + @Test + public void testCalculateVisibleInsets_default() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 100, 0, 0), insets); + } + + @Test + public void testCalculateVisibleInsets_override() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisibleFrame(new Rect(0, 0, 500, 200)); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 200, 0, 0), insets); + } + + @Test + public void testCalculateVisibleInsets_invisible() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisibleFrame(new Rect(0, 0, 500, 200)); + mSource.setVisible(false); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 0, 0, 0), insets); + } + + // Parcel and equals already tested via InsetsStateTest } diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index fa2ffccaaa63..4b76fee00496 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -18,12 +18,14 @@ package android.view; import static android.view.InsetsState.ISIDE_BOTTOM; import static android.view.InsetsState.ISIDE_TOP; +import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.WindowInsets.Type.ime; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; @@ -209,6 +211,42 @@ public class InsetsStateTest { assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME)); } + @Test + public void testCalculateVisibleInsets() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(ITYPE_IME).setVisible(true); + + // Make sure bottom gestures are ignored + mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN); + assertEquals(new Rect(0, 100, 0, 100), visibleInsets); + } + } + + @Test + public void testCalculateVisibleInsets_adjustNothing() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(ITYPE_IME).setVisible(true); + + // Make sure bottom gestures are ignored + mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING); + assertEquals(new Rect(0, 100, 0, 0), visibleInsets); + } + } + private void assertEqualsAndHashCode() { assertEquals(mState, mState2); assertEquals(mState.hashCode(), mState2.hashCode()); diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index f497db2256dd..89c237498e5c 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -226,6 +226,61 @@ public class EditorCursorDragTest { } @Test + public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable { + String text = "Hi world!"; + onView(withId(R.id.textview)).perform(replaceText(text)); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); + + TextView tv = mActivity.findViewById(R.id.textview); + Editor editor = tv.getEditorForTesting(); + + // Simulate a tap-and-drag gesture. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event2Time = 1002; + MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); + assertTrue(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event3Time = 1003; + MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); + assertTrue(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event4Time = 2004; + MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + // Simulate a quick tap after the drag, near the location where the drag ended. + long event5Time = 2005; + MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event6Time = 2006; + MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + // Simulate another quick tap in the same location; now selection should be triggered. + long event7Time = 2007; + MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertTrue(editor.getSelectionController().isCursorBeingModified()); + } + + @Test public void testEditor_onTouchEvent_cursorDrag() throws Throwable { String text = "testEditor_onTouchEvent_cursorDrag"; onView(withId(R.id.textview)).perform(replaceText(text)); @@ -237,29 +292,25 @@ public class EditorCursorDragTest { // Simulate a tap-and-drag gesture. This should trigger a cursor drag. long event1Time = 1001; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event1)); - mInstrumentation.waitForIdleSync(); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event2Time = 1002; MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event2)); - mInstrumentation.waitForIdleSync(); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event3Time = 1003; - MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event3)); - mInstrumentation.waitForIdleSync(); + MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); assertTrue(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event4Time = 1004; - MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event4)); - mInstrumentation.waitForIdleSync(); + MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); } @@ -276,36 +327,31 @@ public class EditorCursorDragTest { // Simulate a double-tap followed by a drag. This should trigger a selection drag. long event1Time = 1001; MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event1)); - mInstrumentation.waitForIdleSync(); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event2Time = 1002; MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event2)); - mInstrumentation.waitForIdleSync(); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); long event3Time = 1003; MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event3)); - mInstrumentation.waitForIdleSync(); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertTrue(editor.getSelectionController().isCursorBeingModified()); long event4Time = 1004; MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event4)); - mInstrumentation.waitForIdleSync(); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertTrue(editor.getSelectionController().isCursorBeingModified()); long event5Time = 1005; MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f); - mActivity.runOnUiThread(() -> editor.onTouchEvent(event5)); - mInstrumentation.waitForIdleSync(); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5)); assertFalse(editor.getInsertionController().isCursorBeingModified()); assertFalse(editor.getSelectionController().isCursorBeingModified()); } diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java index 6adb1b8fa0d6..215d0b800074 100644 --- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java +++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java @@ -120,6 +120,60 @@ public class EditorTouchStateTest { } @Test + public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1000; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + + // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout. + long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1; + MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); + mTouchState.update(event2, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + + // Generate an ACTION_DOWN event whose time is within the double-tap timeout when + // calculated from the last ACTION_UP event time. Even though the time between the last up + // and this down event is within the double-tap timeout, this should not be considered a + // double-tap (since the first down event had a longer delay). + long event3Time = event2Time + 1; + MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f); + mTouchState.update(event3, mConfig); + assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false); + } + + @Test + public void testUpdate_quickTapAfterDrag() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1000; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + + // Simulate an ACTION_MOVE event. + long event2Time = 1001; + MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f); + mTouchState.update(event2, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0, true); + + // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout. + long event3Time = 5000; + MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f); + mTouchState.update(event3, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false); + + // Generate an ACTION_DOWN event whose time is within the double-tap timeout when + // calculated from the last ACTION_UP event time. Even though the time between the last up + // and this down event is within the double-tap timeout, this should not be considered a + // double-tap (since the first down event had a longer delay). + long event4Time = event3Time + 1; + MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f); + mTouchState.update(event4, mConfig); + assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false); + } + + @Test public void testUpdate_tripleClick_mouse() throws Exception { // Simulate an ACTION_DOWN event. long event1Time = 1000; diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index eb1d1ab1089c..9930ea262b32 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -243,6 +243,7 @@ applications that come with the platform <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> + <permission name="android.permission.TETHER_PRIVILEGED"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> </privapp-permissions> diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d945fc49ec88..a7f8cc4688ef 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -215,6 +215,7 @@ cc_defaults { android: { srcs: [ + "pipeline/skia/ATraceMemoryDump.cpp", "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", "pipeline/skia/ShaderCache.cpp", @@ -244,7 +245,6 @@ cc_defaults { "DeviceInfo.cpp", "FrameInfo.cpp", "FrameInfoVisualizer.cpp", - "GpuMemoryTracker.cpp", "HardwareBitmapUploader.cpp", "HWUIProperties.sysprop", "JankTracker.cpp", @@ -325,7 +325,6 @@ cc_test { "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", "tests/unit/FatVectorTests.cpp", - "tests/unit/GpuMemoryTrackerTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", "tests/unit/LayerUpdateQueueTests.cpp", "tests/unit/LinearAllocatorTests.cpp", diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp deleted file mode 100644 index a9a7af8f22f3..000000000000 --- a/libs/hwui/GpuMemoryTracker.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "utils/StringUtils.h" - -#include <GpuMemoryTracker.h> -#include <cutils/compiler.h> -#include <utils/Trace.h> -#include <array> -#include <sstream> -#include <unordered_set> -#include <vector> - -namespace android { -namespace uirenderer { - -pthread_t gGpuThread = 0; - -#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount) - -const char* TYPE_NAMES[] = { - "Texture", "OffscreenBuffer", "Layer", -}; - -struct TypeStats { - int totalSize = 0; - int count = 0; -}; - -static std::array<TypeStats, NUM_TYPES> gObjectStats; -static std::unordered_set<GpuMemoryTracker*> gObjectSet; - -void GpuMemoryTracker::notifySizeChanged(int newSize) { - int delta = newSize - mSize; - mSize = newSize; - gObjectStats[static_cast<int>(mType)].totalSize += delta; -} - -void GpuMemoryTracker::startTrackingObject() { - auto result = gObjectSet.insert(this); - LOG_ALWAYS_FATAL_IF(!result.second, - "startTrackingObject() on %p failed, already being tracked!", this); - gObjectStats[static_cast<int>(mType)].count++; -} - -void GpuMemoryTracker::stopTrackingObject() { - size_t removed = gObjectSet.erase(this); - LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?", - removed, this); - gObjectStats[static_cast<int>(mType)].count--; -} - -void GpuMemoryTracker::onGpuContextCreated() { - LOG_ALWAYS_FATAL_IF(gGpuThread != 0, - "We already have a gpu thread? " - "current = %lu, gpu thread = %lu", - pthread_self(), gGpuThread); - gGpuThread = pthread_self(); -} - -void GpuMemoryTracker::onGpuContextDestroyed() { - gGpuThread = 0; - if (CC_UNLIKELY(gObjectSet.size() > 0)) { - std::stringstream os; - dump(os); - ALOGE("%s", os.str().c_str()); - LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size()); - } -} - -void GpuMemoryTracker::dump() { - std::stringstream strout; - dump(strout); - ALOGD("%s", strout.str().c_str()); -} - -void GpuMemoryTracker::dump(std::ostream& stream) { - for (int type = 0; type < NUM_TYPES; type++) { - const TypeStats& stats = gObjectStats[type]; - stream << TYPE_NAMES[type]; - stream << " is using " << SizePrinter{stats.totalSize}; - stream << ", count = " << stats.count; - stream << std::endl; - } -} - -int GpuMemoryTracker::getInstanceCount(GpuObjectType type) { - return gObjectStats[static_cast<int>(type)].count; -} - -int GpuMemoryTracker::getTotalSize(GpuObjectType type) { - return gObjectStats[static_cast<int>(type)].totalSize; -} - -void GpuMemoryTracker::onFrameCompleted() { - if (ATRACE_ENABLED()) { - char buf[128]; - for (int type = 0; type < NUM_TYPES; type++) { - snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]); - const TypeStats& stats = gObjectStats[type]; - ATRACE_INT(buf, stats.totalSize); - snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]); - ATRACE_INT(buf, stats.count); - } - } -} - -} // namespace uirenderer -} // namespace android; diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h deleted file mode 100644 index de3ca99ef14b..000000000000 --- a/libs/hwui/GpuMemoryTracker.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2016 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. - */ -#pragma once - -#include <pthread.h> -#include <ostream> - -#include <log/log.h> - -namespace android { -namespace uirenderer { - -extern pthread_t gGpuThread; - -#define ASSERT_GPU_THREAD() \ - LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()), \ - "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \ - "!= gpu thread %lu", \ - this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread) - -enum class GpuObjectType { - Texture = 0, - OffscreenBuffer, - Layer, - - TypeCount, -}; - -class GpuMemoryTracker { -public: - GpuObjectType objectType() { return mType; } - int objectSize() { return mSize; } - - static void onGpuContextCreated(); - static void onGpuContextDestroyed(); - static void dump(); - static void dump(std::ostream& stream); - static int getInstanceCount(GpuObjectType type); - static int getTotalSize(GpuObjectType type); - static void onFrameCompleted(); - -protected: - explicit GpuMemoryTracker(GpuObjectType type) : mType(type) { - ASSERT_GPU_THREAD(); - startTrackingObject(); - } - - ~GpuMemoryTracker() { - notifySizeChanged(0); - stopTrackingObject(); - } - - void notifySizeChanged(int newSize); - -private: - void startTrackingObject(); - void stopTrackingObject(); - - int mSize = 0; - GpuObjectType mType; -}; - -} // namespace uirenderer -} // namespace android; diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp index 7921662b213c..a8e36e37905d 100644 --- a/libs/hwui/ProfileData.cpp +++ b/libs/hwui/ProfileData.cpp @@ -15,6 +15,7 @@ */ #include "ProfileData.h" +#include "Properties.h" #include <cinttypes> @@ -102,6 +103,7 @@ void ProfileData::mergeWith(const ProfileData& other) { mGPUFrameCounts[i] >>= divider; mGPUFrameCounts[i] += other.mGPUFrameCounts[i]; } + mPipelineType = other.mPipelineType; } void ProfileData::dump(int fd) const { @@ -157,6 +159,7 @@ void ProfileData::reset() { mTotalFrameCount = 0; mJankFrameCount = 0; mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC); + mPipelineType = Properties::getRenderPipelineType(); } void ProfileData::reportFrame(int64_t duration) { diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h index ccbffc6f136e..dd3ba661dd29 100644 --- a/libs/hwui/ProfileData.h +++ b/libs/hwui/ProfileData.h @@ -16,6 +16,7 @@ #pragma once +#include "Properties.h" #include "utils/Macros.h" #include <utils/Timers.h> @@ -65,6 +66,7 @@ public: uint32_t jankFrameCount() const { return mJankFrameCount; } nsecs_t statsStartTime() const { return mStatStartTime; } uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; } + RenderPipelineType pipelineType() const { return mPipelineType; } struct HistogramEntry { uint32_t renderTimeMs; @@ -103,6 +105,9 @@ private: uint32_t mTotalFrameCount; uint32_t mJankFrameCount; nsecs_t mStatStartTime; + + // true if HWUI renders with Vulkan pipeline + RenderPipelineType mPipelineType; }; // For testing diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp new file mode 100644 index 000000000000..2c78b024536b --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#include "ATraceMemoryDump.h" + +#include <utils/Trace.h> + +#include <cstring> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +// When purgeable is INVALID_TIME it won't be logged at all. +#define INVALID_TIME -1 + +/** + * Skia invokes the following SkTraceMemoryDump functions: + * 1. dumpNumericValue (dumpName, units="bytes", valueName="size") + * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not + * invoke dumpStringValue] + * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional] + * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not + * invoke setMemoryBacking] + * + * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to + * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking. + * Only GPU Texture memory is tracked separately and everything else is grouped as one + * "GPU Memory" category. + */ +static std::unordered_map<const char*, const char*> sResourceMap = { + {"malloc", "Graphics CPU Memory"}, // taken from setMemoryBacking(backingType) + {"gl_texture", "Graphics Texture Memory"}, // taken from setMemoryBacking(backingType) + {"Texture", + "Graphics Texture Memory"}, // taken from dumpStringValue(value, valueName="type") + // Uncomment categories below to split "GPU Memory" into more brackets for debugging. + /*{"vk_buffer", "vk_buffer"}, + {"gl_renderbuffer", "gl_renderbuffer"}, + {"gl_buffer", "gl_buffer"}, + {"RenderTarget", "RenderTarget"}, + {"Stencil", "Stencil"}, + {"Path Data", "Path Data"}, + {"Buffer Object", "Buffer Object"}, + {"Surface", "Surface"},*/ +}; + +ATraceMemoryDump::ATraceMemoryDump() { + mLastDumpName.reserve(100); + mCategory.reserve(100); +} + +void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName, + const char* units, uint64_t value) { + if (!strcmp(units, "bytes")) { + recordAndResetCountersIfNeeded(dumpName); + if (!strcmp(valueName, "size")) { + mLastDumpValue = value; + } else if (!strcmp(valueName, "purgeable_size")) { + mLastPurgeableDumpValue = value; + } + } +} + +void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName, + const char* value) { + if (!strcmp(valueName, "type")) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(value); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } + } +} + +void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(backingType); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } +} + +/** + * startFrame is invoked before dumping anything. It resets counters from the previous frame. + * This is important, because if there is no new data for a given category trace would assume + * usage has not changed (instead of reporting 0). + */ +void ATraceMemoryDump::startFrame() { + resetCurrentCounter(""); + for (auto& it : mCurrentValues) { + // Once a category is observed in at least one frame, it is always reported in subsequent + // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not + // changed since the previous frame, which is not what we want. + it.second.time = 0; + // If purgeableTime is INVALID_TIME, then logTraces won't log it at all. + if (it.second.purgeableTime != INVALID_TIME) { + it.second.purgeableTime = 0; + } + } +} + +/** + * logTraces reads from mCurrentValues and logs the counters with ATRACE. + */ +void ATraceMemoryDump::logTraces() { + // Accumulate data from last dumpName + recordAndResetCountersIfNeeded(""); + for (auto& it : mCurrentValues) { + ATRACE_INT64(it.first.c_str(), it.second.time); + if (it.second.purgeableTime != INVALID_TIME) { + ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime); + } + } +} + +/** + * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and + * accumulates in mCurrentValues[category]. It makes provision to create a new category and track + * purgeable memory only if there is at least one observation. + * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName + * is received. + */ +void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) { + if (!mLastDumpName.compare(dumpName)) { + // Still waiting for more data for current dumpName. + return; + } + + // First invocation will have an empty mLastDumpName. + if (!mLastDumpName.empty()) { + // A new dumpName observed -> store the data already collected. + auto memoryCounter = mCurrentValues.find(mCategory); + if (memoryCounter != mCurrentValues.end()) { + memoryCounter->second.time += mLastDumpValue; + if (mLastPurgeableDumpValue != INVALID_TIME) { + if (memoryCounter->second.purgeableTime == INVALID_TIME) { + memoryCounter->second.purgeableTime = mLastPurgeableDumpValue; + } else { + memoryCounter->second.purgeableTime += mLastPurgeableDumpValue; + } + } + } else { + mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue}; + } + } + + // Reset counters and default category for the newly observed "dumpName". + resetCurrentCounter(dumpName); +} + +void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) { + mLastDumpValue = 0; + mLastPurgeableDumpValue = INVALID_TIME; + mLastDumpName = dumpName; + // Categories not listed in sResourceMap are reported as "GPU memory" + mCategory = "GPU Memory"; +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h new file mode 100644 index 000000000000..aa5c401ad1ca --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include <SkString.h> +#include <SkTraceMemoryDump.h> + +#include <string> +#include <unordered_map> +#include <utility> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class ATraceMemoryDump : public SkTraceMemoryDump { +public: + ATraceMemoryDump(); + ~ATraceMemoryDump() override {} + + void dumpNumericValue(const char* dumpName, const char* valueName, const char* units, + uint64_t value) override; + + void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override; + + LevelOfDetail getRequestedDetails() const override { + return SkTraceMemoryDump::kLight_LevelOfDetail; + } + + bool shouldDumpWrappedObjects() const override { return false; } + + void setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) override; + + void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {} + + void startFrame(); + + void logTraces(); + +private: + std::string mLastDumpName; + + uint64_t mLastDumpValue; + + uint64_t mLastPurgeableDumpValue; + + std::string mCategory; + + struct TraceValue { + uint64_t time; + uint64_t purgeableTime; + }; + + // keys are define in sResourceMap + std::unordered_map<std::string, TraceValue> mCurrentValues; + + void recordAndResetCountersIfNeeded(const char* dumpName); + + void resetCurrentCounter(const char* dumpName); +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 11dc013af6bc..35a885f46919 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -457,7 +457,10 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, const Rect& contentDrawBounds, SkCanvas* canvas, const SkMatrix& preTransform) { SkAutoCanvasRestore saver(canvas, true); - canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut()); + auto clipRestriction = preTransform.mapRect(clip).roundOut(); + canvas->androidFramework_setDeviceClipRestriction(clipRestriction); + canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction", + nullptr); canvas->concat(preTransform); // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293 diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto index 0cd5c6228504..dd5676c9c961 100644 --- a/libs/hwui/protos/graphicsstats.proto +++ b/libs/hwui/protos/graphicsstats.proto @@ -29,6 +29,11 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } + // The package name of the app optional string package_name = 1; @@ -49,6 +54,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index fad9440be73f..7e8c96d96860 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -16,7 +16,6 @@ #include "renderstate/RenderState.h" #include "renderthread/RenderThread.h" -#include "GpuMemoryTracker.h" namespace android { namespace uirenderer { @@ -25,15 +24,10 @@ RenderState::RenderState(renderthread::RenderThread& thread) : mRenderThread(thr mThreadId = pthread_self(); } -void RenderState::onContextCreated() { - GpuMemoryTracker::onGpuContextCreated(); -} - void RenderState::onContextDestroyed() { for(auto callback : mContextCallbacks) { callback->onContextDestroyed(); } - GpuMemoryTracker::onGpuContextDestroyed(); } void RenderState::postDecStrong(VirtualLightRefBase* object) { diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index ff5d02fe359a..e08d32a7735c 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -62,7 +62,6 @@ private: ~RenderState() {} // Context notifications are only to be triggered by renderthread::RenderThread - void onContextCreated(); void onContextDestroyed(); std::set<IGpuContextCallback*> mContextCallbacks; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index eaed46c44e5d..d177855e5a7d 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -20,10 +20,12 @@ #include "Layer.h" #include "Properties.h" #include "RenderThread.h" +#include "pipeline/skia/ATraceMemoryDump.h" #include "pipeline/skia/ShaderCache.h" #include "pipeline/skia/SkiaMemoryTracer.h" #include "renderstate/RenderState.h" #include "thread/CommonPool.h" +#include <utils/Trace.h> #include <GrContextOptions.h> #include <SkExecutor.h> @@ -184,6 +186,18 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) gpuTracer.logTotals(log); } +void CacheManager::onFrameCompleted() { + if (ATRACE_ENABLED()) { + static skiapipeline::ATraceMemoryDump tracer; + tracer.startFrame(); + SkGraphics::DumpMemoryStatistics(&tracer); + if (mGrContext) { + mGrContext->dumpMemoryStatistics(&tracer); + } + tracer.logTraces(); + } +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 968251e9f467..b009cc4f48f2 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -50,6 +50,7 @@ public: size_t getCacheSize() const { return mMaxResourceBytes; } size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; } + void onFrameCompleted(); private: friend class RenderThread; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 84902210a751..5993e176f0b8 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -16,7 +16,6 @@ #include "CanvasContext.h" -#include <GpuMemoryTracker.h> #include <apex/window.h> #include <fcntl.h> #include <strings.h> @@ -558,7 +557,7 @@ void CanvasContext::draw() { mJankTracker.finishGpuDraw(*forthBehind); } - GpuMemoryTracker::onFrameCompleted(); + mRenderThread.cacheManager().onFrameCompleted(); } // Called by choreographer to do an RT-driven animation diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index d78f641d45b9..cae3e3b5188c 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -270,7 +270,6 @@ void RenderThread::setGrContext(sk_sp<GrContext> context) { } mGrContext = std::move(context); if (mGrContext) { - mRenderState->onContextCreated(); DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize()); } } diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index 12c5b836f711..c4186174b637 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -16,24 +16,28 @@ #include "GraphicsStatsService.h" -#include "JankTracker.h" -#include "protos/graphicsstats.pb.h" - -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> -#include <log/log.h> - #include <errno.h> #include <fcntl.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <inttypes.h> +#include <log/log.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <algorithm> +#include <map> +#include <vector> + +#include "JankTracker.h" +#include "protos/graphicsstats.pb.h" + namespace android { namespace uirenderer { using namespace google::protobuf; +using namespace uirenderer::protos; constexpr int32_t sCurrentFileVersion = 1; constexpr int32_t sHeaderSize = 4; @@ -42,9 +46,9 @@ static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong" constexpr int sHistogramSize = ProfileData::HistogramSize(); constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize(); -static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, - const std::string& package, int64_t versionCode, - int64_t startTime, int64_t endTime, const ProfileData* data); +static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, + int64_t versionCode, int64_t startTime, int64_t endTime, + const ProfileData* data); static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd); class FileDescriptor { @@ -57,7 +61,7 @@ public: } } bool valid() { return mFd != -1; } - operator int() { return mFd; } // NOLINT(google-explicit-constructor) + operator int() { return mFd; } // NOLINT(google-explicit-constructor) private: int mFd; @@ -167,6 +171,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str } proto->set_package_name(package); proto->set_version_code(versionCode); + proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ? + GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN); auto summary = proto->mutable_summary(); summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); @@ -179,8 +185,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync)); summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); - summary->set_missed_deadline_count(summary->missed_deadline_count() - + data->jankTypeCount(kMissedDeadline)); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + data->jankTypeCount(kMissedDeadline)); bool creatingHistogram = false; if (proto->histogram_size() == 0) { @@ -365,17 +371,69 @@ void GraphicsStatsService::saveBuffer(const std::string& path, const std::string class GraphicsStatsService::Dump { public: - Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} + Dump(int outFd, DumpType type) : mFd(outFd), mType(type) { + if (mFd == -1 && mType == DumpType::Protobuf) { + mType = DumpType::ProtobufStatsd; + } + } int fd() { return mFd; } DumpType type() { return mType; } protos::GraphicsStatsServiceDumpProto& proto() { return mProto; } + void mergeStat(const protos::GraphicsStatsProto& stat); + void updateProto(); private: + // use package name and app version for a key + typedef std::pair<std::string, int64_t> DumpKey; + + std::map<DumpKey, protos::GraphicsStatsProto> mStats; int mFd; DumpType mType; protos::GraphicsStatsServiceDumpProto mProto; }; +void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) { + auto dumpKey = std::make_pair(stat.package_name(), stat.version_code()); + auto findIt = mStats.find(dumpKey); + if (findIt == mStats.end()) { + mStats[dumpKey] = stat; + } else { + auto summary = findIt->second.mutable_summary(); + summary->set_total_frames(summary->total_frames() + stat.summary().total_frames()); + summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames()); + summary->set_missed_vsync_count(summary->missed_vsync_count() + + stat.summary().missed_vsync_count()); + summary->set_high_input_latency_count(summary->high_input_latency_count() + + stat.summary().high_input_latency_count()); + summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() + + stat.summary().slow_ui_thread_count()); + summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + + stat.summary().slow_bitmap_upload_count()); + summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count()); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + stat.summary().missed_deadline_count()); + for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) { + auto bucket = findIt->second.mutable_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.histogram(bucketIndex).frame_count()); + } + for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size(); + bucketIndex++) { + auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.gpu_histogram(bucketIndex).frame_count()); + } + findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start())); + findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end())); + } +} + +void GraphicsStatsService::Dump::updateProto() { + for (auto& stat : mStats) { + mProto.add_stats()->CopyFrom(stat.second); + } +} + GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { return new Dump(outFd, type); } @@ -396,8 +454,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, path.empty() ? "<empty>" : path.c_str(), data); return; } - - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -409,7 +468,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { if (!parseFromFile(path, &statsProto)) { return; } - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -424,5 +485,79 @@ void GraphicsStatsService::finishDump(Dump* dump) { delete dump; } +class MemOutputStreamLite : public io::ZeroCopyOutputStream { +public: + explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {} + virtual ~MemOutputStreamLite() {} + + virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); } + + virtual void BackUp(int count) override { mImpl.BackUp(count); } + + virtual int64 ByteCount() const override { return mImpl.ByteCount(); } + + bool Flush() { return mImpl.Flush(); } + + void copyData(const DumpMemoryFn& reader, void* param1, void* param2) { + int bufferOffset = 0; + int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize; + int totalDataLeft = totalSize; + for (auto& it : mCopyAdapter.mBuffers) { + int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full + reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2); + bufferOffset += bufferSize; + totalDataLeft -= bufferSize; + } + } + +private: + struct MemAdapter : public io::CopyingOutputStream { + // Data is stored in an array of buffers. + // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer. + std::vector<std::vector<unsigned char>> mBuffers; + int mBuffersSize = 0; // total bytes allocated in mBuffers + int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back() + unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back() + + explicit MemAdapter() {} + virtual ~MemAdapter() {} + + virtual bool Write(const void* buffer, int size) override { + while (size > 0) { + if (0 == mCurrentBufferUnusedSize) { + mCurrentBufferUnusedSize = + std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000); + mBuffers.emplace_back(); + mBuffers.back().resize(mCurrentBufferUnusedSize); + mCurrentBuffer = mBuffers.back().data(); + mBuffersSize += mCurrentBufferUnusedSize; + } + int dataMoved = std::min(mCurrentBufferUnusedSize, size); + memcpy(mCurrentBuffer, buffer, dataMoved); + mCurrentBufferUnusedSize -= dataMoved; + mCurrentBuffer += dataMoved; + buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved; + size -= dataMoved; + } + return true; + } + }; + + MemOutputStreamLite::MemAdapter mCopyAdapter; + io::CopyingOutputStreamAdaptor mImpl; +}; + +void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2) { + MemOutputStreamLite stream; + dump->updateProto(); + bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush(); + delete dump; + if (!success) { + return; + } + stream.copyData(reader, param1, param2); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h index 389f599f439f..4bed96330a52 100644 --- a/libs/hwui/service/GraphicsStatsService.h +++ b/libs/hwui/service/GraphicsStatsService.h @@ -27,6 +27,9 @@ namespace protos { class GraphicsStatsProto; } +typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize, + void* param1, void* param2); + /* * The exported entry points used by GraphicsStatsService.java in f/b/services/core * @@ -40,6 +43,7 @@ public: enum class DumpType { Text, Protobuf, + ProtobufStatsd, }; ANDROID_API static void saveBuffer(const std::string& path, const std::string& package, @@ -52,6 +56,8 @@ public: int64_t startTime, int64_t endTime, const ProfileData* data); ANDROID_API static void addToDump(Dump* dump, const std::string& path); ANDROID_API static void finishDump(Dump* dump); + ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2); // Visible for testing static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output); diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp deleted file mode 100644 index dac888cd79ca..000000000000 --- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <GpuMemoryTracker.h> -#include <gtest/gtest.h> - -#include "renderthread/EglManager.h" -#include "renderthread/RenderThread.h" -#include "tests/common/TestUtils.h" - -#include <utils/StrongPointer.h> - -using namespace android; -using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; - -class TestGPUObject : public GpuMemoryTracker { -public: - TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {} - - void changeSize(int newSize) { notifySizeChanged(newSize); } -}; - -// Other tests may have created a renderthread and EGL context. -// This will destroy the EGLContext on RenderThread if it exists so that the -// current thread can spoof being a GPU thread -static void destroyEglContext() { - if (TestUtils::isRenderThreadRunning()) { - TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); }); - } -} - -TEST(GpuMemoryTracker, sizeCheck) { - destroyEglContext(); - - GpuMemoryTracker::onGpuContextCreated(); - ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - { - TestGPUObject myObj; - ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - myObj.changeSize(500); - ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - myObj.changeSize(1000); - ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - myObj.changeSize(300); - ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - } - ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - GpuMemoryTracker::onGpuContextDestroyed(); -} diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java index 1370b1095ae1..26f73f784879 100644 --- a/location/java/android/location/Criteria.java +++ b/location/java/android/location/Criteria.java @@ -16,9 +16,16 @@ package android.location; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class indicating the application criteria for selecting a * location provider. Providers may be ordered according to accuracy, @@ -26,6 +33,25 @@ import android.os.Parcelable; * cost. */ public class Criteria implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH}) + public @interface AccuracyRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE}) + public @interface LocationAccuracyRequirement { + } + /** * A constant indicating that the application does not choose to * place requirement on a particular feature. @@ -81,15 +107,15 @@ public class Criteria implements Parcelable { */ public static final int ACCURACY_HIGH = 3; - private int mHorizontalAccuracy = NO_REQUIREMENT; - private int mVerticalAccuracy = NO_REQUIREMENT; - private int mSpeedAccuracy = NO_REQUIREMENT; - private int mBearingAccuracy = NO_REQUIREMENT; - private int mPowerRequirement = NO_REQUIREMENT; - private boolean mAltitudeRequired = false; - private boolean mBearingRequired = false; - private boolean mSpeedRequired = false; - private boolean mCostAllowed = false; + private int mHorizontalAccuracy = NO_REQUIREMENT; + private int mVerticalAccuracy = NO_REQUIREMENT; + private int mSpeedAccuracy = NO_REQUIREMENT; + private int mBearingAccuracy = NO_REQUIREMENT; + private int mPowerRequirement = NO_REQUIREMENT; + private boolean mAltitudeRequired = false; + private boolean mBearingRequired = false; + private boolean mSpeedRequired = false; + private boolean mCostAllowed = false; /** * Constructs a new Criteria object. The new object will have no @@ -97,7 +123,8 @@ public class Criteria implements Parcelable { * require altitude, speed, or bearing; and will not allow monetary * cost. */ - public Criteria() {} + public Criteria() { + } /** * Constructs a new Criteria object that is a copy of the given criteria. @@ -115,125 +142,121 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be + * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or + * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setHorizontalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mHorizontalAccuracy = accuracy; + public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) { + mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. + * + * @see #setHorizontalAccuracy(int) */ + @AccuracyRequirement public int getHorizontalAccuracy() { return mHorizontalAccuracy; } /** - * Indicates the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setVerticalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mVerticalAccuracy = accuracy; + public void setVerticalAccuracy(@AccuracyRequirement int accuracy) { + mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setVerticalAccuracy(int) */ + @AccuracyRequirement public int getVerticalAccuracy() { return mVerticalAccuracy; } /** - * Indicates the desired speed accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setSpeedAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mSpeedAccuracy = accuracy; + public void setSpeedAccuracy(@AccuracyRequirement int accuracy) { + mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH, + "accuracy"); } /** - * Returns a constant indicating the desired speed accuracy - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * Returns a constant indicating the desired speed accuracy. + * + * @see #setSpeedAccuracy(int) */ + @AccuracyRequirement public int getSpeedAccuracy() { return mSpeedAccuracy; } /** - * Indicates the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setBearingAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mBearingAccuracy = accuracy; + public void setBearingAccuracy(@AccuracyRequirement int accuracy) { + mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setBearingAccuracy(int) */ + @AccuracyRequirement public int getBearingAccuracy() { return mBearingAccuracy; } /** - * Indicates the desired accuracy for latitude and longitude. Accuracy - * may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. - * More accurate location may consume more power and may take longer. + * Indicates the desired accuracy for latitude and longitude. Accuracy may be + * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more + * power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - if (accuracy == ACCURACY_FINE) { - mHorizontalAccuracy = ACCURACY_HIGH; - } else { - mHorizontalAccuracy = ACCURACY_LOW; + public void setAccuracy(@LocationAccuracyRequirement int accuracy) { + Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy"); + switch (accuracy) { + case NO_REQUIREMENT: + setHorizontalAccuracy(NO_REQUIREMENT); + break; + case ACCURACY_FINE: + setHorizontalAccuracy(ACCURACY_HIGH); + break; + case ACCURACY_COARSE: + setHorizontalAccuracy(ACCURACY_LOW); + break; } } /** - * Returns a constant indicating desired accuracy of location - * Accuracy may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. + * Returns a constant indicating desired accuracy of location. + * + * @see #setAccuracy(int) */ + @LocationAccuracyRequirement public int getAccuracy() { if (mHorizontalAccuracy >= ACCURACY_HIGH) { return ACCURACY_FINE; @@ -243,21 +266,20 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired maximum power level. The level parameter - * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or - * POWER_HIGH. + * Indicates the desired maximum power requirement. The power requirement parameter may be + * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}. */ - public void setPowerRequirement(int level) { - if (level < NO_REQUIREMENT || level > POWER_HIGH) { - throw new IllegalArgumentException("level=" + level); - } - mPowerRequirement = level; + public void setPowerRequirement(@PowerRequirement int powerRequirement) { + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT, + POWER_HIGH, "powerRequirement"); } /** - * Returns a constant indicating the desired power requirement. The - * returned + * Returns a constant indicating the desired maximum power requirement. + * + * @see #setPowerRequirement(int) */ + @PowerRequirement public int getPowerRequirement() { return mPowerRequirement; } @@ -277,8 +299,8 @@ public class Criteria implements Parcelable { } /** - * Indicates whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide altitude information. Not all fixes are + * guaranteed to contain such information. */ public void setAltitudeRequired(boolean altitudeRequired) { mAltitudeRequired = altitudeRequired; @@ -286,15 +308,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setAltitudeRequired(boolean) */ public boolean isAltitudeRequired() { return mAltitudeRequired; } /** - * Indicates whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide speed information. Not all fixes are guaranteed + * to contain such information. */ public void setSpeedRequired(boolean speedRequired) { mSpeedRequired = speedRequired; @@ -302,15 +325,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setSpeedRequired(boolean) */ public boolean isSpeedRequired() { return mSpeedRequired; } /** - * Indicates whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed + * to contain such information. */ public void setBearingRequired(boolean bearingRequired) { mBearingRequired = bearingRequired; @@ -318,34 +342,36 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setBearingRequired(boolean) */ public boolean isBearingRequired() { return mBearingRequired; } - public static final @android.annotation.NonNull Parcelable.Creator<Criteria> CREATOR = - new Parcelable.Creator<Criteria>() { - @Override - public Criteria createFromParcel(Parcel in) { - Criteria c = new Criteria(); - c.mHorizontalAccuracy = in.readInt(); - c.mVerticalAccuracy = in.readInt(); - c.mSpeedAccuracy = in.readInt(); - c.mBearingAccuracy = in.readInt(); - c.mPowerRequirement = in.readInt(); - c.mAltitudeRequired = in.readInt() != 0; - c.mBearingRequired = in.readInt() != 0; - c.mSpeedRequired = in.readInt() != 0; - c.mCostAllowed = in.readInt() != 0; - return c; - } - - @Override - public Criteria[] newArray(int size) { - return new Criteria[size]; - } - }; + @NonNull + public static final Parcelable.Creator<Criteria> CREATOR = + new Parcelable.Creator<Criteria>() { + @Override + public Criteria createFromParcel(Parcel in) { + Criteria c = new Criteria(); + c.mHorizontalAccuracy = in.readInt(); + c.mVerticalAccuracy = in.readInt(); + c.mSpeedAccuracy = in.readInt(); + c.mBearingAccuracy = in.readInt(); + c.mPowerRequirement = in.readInt(); + c.mAltitudeRequired = in.readInt() != 0; + c.mBearingRequired = in.readInt() != 0; + c.mSpeedRequired = in.readInt() != 0; + c.mCostAllowed = in.readInt() != 0; + return c; + } + + @Override + public Criteria[] newArray(int size) { + return new Criteria[size]; + } + }; @Override public int describeContents() { @@ -365,42 +391,57 @@ public class Criteria implements Parcelable { parcel.writeInt(mCostAllowed ? 1 : 0); } - private static String powerToString(int power) { + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Criteria["); + s.append("power=").append(requirementToString(mPowerRequirement)).append(", "); + s.append("accuracy=").append(requirementToString(mHorizontalAccuracy)); + if (mVerticalAccuracy != NO_REQUIREMENT) { + s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy)); + } + if (mSpeedAccuracy != NO_REQUIREMENT) { + s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy)); + } + if (mBearingAccuracy != NO_REQUIREMENT) { + s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy)); + } + if (mAltitudeRequired || mBearingRequired || mSpeedRequired) { + s.append(", required=["); + if (mAltitudeRequired) { + s.append("altitude, "); + } + if (mBearingRequired) { + s.append("bearing, "); + } + if (mSpeedRequired) { + s.append("speed, "); + } + s.setLength(s.length() - 2); + s.append("]"); + } + if (mCostAllowed) { + s.append(", costAllowed"); + } + s.append(']'); + return s.toString(); + } + + private static String requirementToString(int power) { switch (power) { case NO_REQUIREMENT: - return "NO_REQ"; + return "None"; + //case ACCURACY_LOW: case POWER_LOW: - return "LOW"; + return "Low"; + //case ACCURACY_MEDIUM: case POWER_MEDIUM: - return "MEDIUM"; + return "Medium"; + //case ACCURACY_HIGH: case POWER_HIGH: - return "HIGH"; + return "High"; default: return "???"; } } - - private static String accuracyToString(int accuracy) { - switch (accuracy) { - case NO_REQUIREMENT: - return "---"; - case ACCURACY_HIGH: - return "HIGH"; - case ACCURACY_MEDIUM: - return "MEDIUM"; - case ACCURACY_LOW: - return "LOW"; - default: - return "???"; - } - } - - @Override - public String toString() { - StringBuilder s = new StringBuilder(); - s.append("Criteria[power=").append(powerToString(mPowerRequirement)); - s.append(" acc=").append(accuracyToString(mHorizontalAccuracy)); - s.append(']'); - return s.toString(); - } } diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java index def96f0fb674..68f9ec3c530b 100644 --- a/location/java/com/android/internal/location/ProviderProperties.java +++ b/location/java/com/android/internal/location/ProviderProperties.java @@ -16,15 +16,36 @@ package com.android.internal.location; +import android.annotation.IntDef; +import android.location.Criteria; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A Parcelable containing (legacy) location provider properties. * This object is just used inside the framework and system services. + * * @hide */ public final class ProviderProperties implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE}) + public @interface Accuracy { + } + /** * True if provider requires access to a * data network (e.g., the Internet), false otherwise. @@ -79,58 +100,58 @@ public final class ProviderProperties implements Parcelable { /** * Power requirement for this provider. - * - * @return the power requirement for this provider, as one of the - * constants Criteria.POWER_*. */ + @PowerRequirement public final int mPowerRequirement; /** * Constant describing the horizontal accuracy returned * by this provider. - * - * @return the horizontal accuracy for this provider, as one of the - * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE */ + @Accuracy public final int mAccuracy; - public ProviderProperties(boolean mRequiresNetwork, - boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost, - boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing, - int mPowerRequirement, int mAccuracy) { - this.mRequiresNetwork = mRequiresNetwork; - this.mRequiresSatellite = mRequiresSatellite; - this.mRequiresCell = mRequiresCell; - this.mHasMonetaryCost = mHasMonetaryCost; - this.mSupportsAltitude = mSupportsAltitude; - this.mSupportsSpeed = mSupportsSpeed; - this.mSupportsBearing = mSupportsBearing; - this.mPowerRequirement = mPowerRequirement; - this.mAccuracy = mAccuracy; + public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement, + @Accuracy int accuracy) { + mRequiresNetwork = requiresNetwork; + mRequiresSatellite = requiresSatellite; + mRequiresCell = requiresCell; + mHasMonetaryCost = hasMonetaryCost; + mSupportsAltitude = supportsAltitude; + mSupportsSpeed = supportsSpeed; + mSupportsBearing = supportsBearing; + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW, + Criteria.POWER_HIGH, "powerRequirement"); + mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE, + Criteria.ACCURACY_COARSE, "accuracy"); } public static final Parcelable.Creator<ProviderProperties> CREATOR = new Parcelable.Creator<ProviderProperties>() { - @Override - public ProviderProperties createFromParcel(Parcel in) { - boolean requiresNetwork = in.readInt() == 1; - boolean requiresSatellite = in.readInt() == 1; - boolean requiresCell = in.readInt() == 1; - boolean hasMonetaryCost = in.readInt() == 1; - boolean supportsAltitude = in.readInt() == 1; - boolean supportsSpeed = in.readInt() == 1; - boolean supportsBearing = in.readInt() == 1; - int powerRequirement = in.readInt(); - int accuracy = in.readInt(); - return new ProviderProperties(requiresNetwork, requiresSatellite, - requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, - powerRequirement, accuracy); - } - @Override - public ProviderProperties[] newArray(int size) { - return new ProviderProperties[size]; - } - }; + @Override + public ProviderProperties createFromParcel(Parcel in) { + boolean requiresNetwork = in.readInt() == 1; + boolean requiresSatellite = in.readInt() == 1; + boolean requiresCell = in.readInt() == 1; + boolean hasMonetaryCost = in.readInt() == 1; + boolean supportsAltitude = in.readInt() == 1; + boolean supportsSpeed = in.readInt() == 1; + boolean supportsBearing = in.readInt() == 1; + int powerRequirement = in.readInt(); + int accuracy = in.readInt(); + return new ProviderProperties(requiresNetwork, requiresSatellite, + requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, + supportsBearing, + powerRequirement, accuracy); + } + + @Override + public ProviderProperties[] newArray(int size) { + return new ProviderProperties[size]; + } + }; @Override public int describeContents() { @@ -149,4 +170,67 @@ public final class ProviderProperties implements Parcelable { parcel.writeInt(mPowerRequirement); parcel.writeInt(mAccuracy); } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("ProviderProperties["); + b.append("power=").append(powerToString(mPowerRequirement)).append(", "); + b.append("accuracy=").append(accuracyToString(mAccuracy)); + if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) { + b.append(", requires="); + if (mRequiresNetwork) { + b.append("network,"); + } + if (mRequiresSatellite) { + b.append("satellite,"); + } + if (mRequiresCell) { + b.append("cell,"); + } + b.setLength(b.length() - 1); + } + if (mHasMonetaryCost) { + b.append(", hasMonetaryCost"); + } + if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) { + b.append(", supports=["); + if (mSupportsBearing) { + b.append("bearing, "); + } + if (mSupportsSpeed) { + b.append("speed, "); + } + if (mSupportsAltitude) { + b.append("altitude, "); + } + b.setLength(b.length() - 2); + b.append("]"); + } + b.append("]"); + return b.toString(); + } + + private static String powerToString(@PowerRequirement int power) { + switch (power) { + case Criteria.POWER_LOW: + return "Low"; + case Criteria.POWER_MEDIUM: + return "Medium"; + case Criteria.POWER_HIGH: + return "High"; + default: + return "???"; + } + } + + private static String accuracyToString(@Accuracy int accuracy) { + switch (accuracy) { + case Criteria.ACCURACY_COARSE: + return "Coarse"; + case Criteria.ACCURACY_FINE: + return "Fine"; + default: + return "???"; + } + } } diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index c23f49976799..8d8df4533ebe 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -20,33 +20,42 @@ import android.compat.annotation.UnsupportedAppUsage; import android.location.LocationRequest; import android.os.Parcel; import android.os.Parcelable; +import android.os.WorkSource; import android.util.TimeUtils; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @hide */ public final class ProviderRequest implements Parcelable { + + public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE, + false, false, + Collections.emptyList(), new WorkSource()); + /** Location reporting is requested (true) */ @UnsupportedAppUsage - public boolean reportLocation = false; + public final boolean reportLocation; /** The smallest requested interval */ @UnsupportedAppUsage - public long interval = Long.MAX_VALUE; + public final long interval; /** - * When this flag is true, providers should ignore all location settings, user consents, power - * restrictions or any other restricting factors and always satisfy this request to the best of - * their ability. This flag should only be used in event of an emergency. + * Whether provider shall make stronger than normal tradeoffs to substantially restrict power + * use. */ - public boolean locationSettingsIgnored = false; + public final boolean lowPowerMode; /** - * Whether provider shall make stronger than normal tradeoffs to substantially restrict power - * use. + * When this flag is true, providers should ignore all location settings, user consents, power + * restrictions or any other restricting factors and always satisfy this request to the best of + * their ability. This flag should only be used in event of an emergency. */ - public boolean lowPowerMode = false; + public final boolean locationSettingsIgnored; /** * A more detailed set of requests. @@ -56,26 +65,37 @@ public final class ProviderRequest implements Parcelable { * low power fast interval request. */ @UnsupportedAppUsage - public final List<LocationRequest> locationRequests = new ArrayList<>(); + public final List<LocationRequest> locationRequests; - @UnsupportedAppUsage - public ProviderRequest() { + public final WorkSource workSource; + + private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode, + boolean locationSettingsIgnored, List<LocationRequest> locationRequests, + WorkSource workSource) { + this.reportLocation = reportLocation; + this.interval = interval; + this.lowPowerMode = lowPowerMode; + this.locationSettingsIgnored = locationSettingsIgnored; + this.locationRequests = Preconditions.checkNotNull(locationRequests); + this.workSource = Preconditions.checkNotNull(workSource); } public static final Parcelable.Creator<ProviderRequest> CREATOR = new Parcelable.Creator<ProviderRequest>() { @Override public ProviderRequest createFromParcel(Parcel in) { - ProviderRequest request = new ProviderRequest(); - request.reportLocation = in.readInt() == 1; - request.interval = in.readLong(); - request.lowPowerMode = in.readBoolean(); - request.locationSettingsIgnored = in.readBoolean(); + boolean reportLocation = in.readInt() == 1; + long interval = in.readLong(); + boolean lowPowerMode = in.readBoolean(); + boolean locationSettingsIgnored = in.readBoolean(); int count = in.readInt(); + ArrayList<LocationRequest> locationRequests = new ArrayList<>(count); for (int i = 0; i < count; i++) { - request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); + locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); } - return request; + WorkSource workSource = in.readParcelable(null); + return new ProviderRequest(reportLocation, interval, lowPowerMode, + locationSettingsIgnored, locationRequests, workSource); } @Override @@ -106,14 +126,13 @@ public final class ProviderRequest implements Parcelable { StringBuilder s = new StringBuilder(); s.append("ProviderRequest["); if (reportLocation) { - s.append("ON"); - s.append(" interval="); + s.append("interval="); TimeUtils.formatDuration(interval, s); if (lowPowerMode) { - s.append(" lowPowerMode"); + s.append(", lowPowerMode"); } if (locationSettingsIgnored) { - s.append(" locationSettingsIgnored"); + s.append(", locationSettingsIgnored"); } } else { s.append("OFF"); @@ -121,4 +140,67 @@ public final class ProviderRequest implements Parcelable { s.append(']'); return s.toString(); } + + /** + * A Builder for {@link ProviderRequest}s. + */ + public static class Builder { + private long mInterval = Long.MAX_VALUE; + private boolean mLowPowerMode; + private boolean mLocationSettingsIgnored; + private List<LocationRequest> mLocationRequests = Collections.emptyList(); + private WorkSource mWorkSource = new WorkSource(); + + public long getInterval() { + return mInterval; + } + + public void setInterval(long interval) { + this.mInterval = interval; + } + + public boolean isLowPowerMode() { + return mLowPowerMode; + } + + public void setLowPowerMode(boolean lowPowerMode) { + this.mLowPowerMode = lowPowerMode; + } + + public boolean isLocationSettingsIgnored() { + return mLocationSettingsIgnored; + } + + public void setLocationSettingsIgnored(boolean locationSettingsIgnored) { + this.mLocationSettingsIgnored = locationSettingsIgnored; + } + + public List<LocationRequest> getLocationRequests() { + return mLocationRequests; + } + + public void setLocationRequests(List<LocationRequest> locationRequests) { + this.mLocationRequests = Preconditions.checkNotNull(locationRequests); + } + + public WorkSource getWorkSource() { + return mWorkSource; + } + + public void setWorkSource(WorkSource workSource) { + mWorkSource = Preconditions.checkNotNull(workSource); + } + + /** + * Builds a ProviderRequest object with the set information. + */ + public ProviderRequest build() { + if (mInterval == Long.MAX_VALUE) { + return EMPTY_REQUEST; + } else { + return new ProviderRequest(true, mInterval, mLowPowerMode, + mLocationSettingsIgnored, mLocationRequests, mWorkSource); + } + } + } } diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 51fa4eeaf4d8..28bf84d6e079 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -24,13 +24,12 @@ import android.media.IMediaRoute2ProviderClient; */ oneway interface IMediaRoute2Provider { void setClient(IMediaRoute2ProviderClient client); - void requestCreateSession(String packageName, String routeId, - String routeType, long requestId); - void releaseSession(int sessionId); + void requestCreateSession(String packageName, String routeId, String routeType, long requestId); + void releaseSession(String sessionId); - void selectRoute(int sessionId, String routeId); - void deselectRoute(int sessionId, String routeId); - void transferToRoute(int sessionId, String routeId); + void selectRoute(String sessionId, String routeId); + void deselectRoute(String sessionId, String routeId); + void transferToRoute(String sessionId, String routeId); void notifyControlRequestSent(String id, in Intent request); void requestSetVolume(String id, int volume); diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 13640a438e7b..1ed53d942b63 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.MediaRouter2Utils.toUniqueId; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -191,13 +193,6 @@ public final class MediaRoute2Info implements Parcelable { } /** - * @hide - */ - public static String toUniqueId(String providerId, String routeId) { - return providerId + ":" + routeId; - } - - /** * Returns true if the route info has all of the required field. * A route info only obtained from {@link com.android.server.media.MediaRouterService} * is valid. diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 91cc44807a73..24b65baebcbd 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -29,6 +29,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -55,7 +56,7 @@ public abstract class MediaRoute2ProviderService extends Service { private MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mSessionLock") - private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); + private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); @@ -106,7 +107,10 @@ public abstract class MediaRoute2ProviderService extends Service { * null if the session is destroyed or id is not valid. */ @Nullable - public final RouteSessionInfo getSessionInfo(int sessionId) { + public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) { + if (TextUtils.isEmpty(sessionId)) { + throw new IllegalArgumentException("sessionId must not be empty"); + } synchronized (mSessionLock) { return mSessionInfo.get(sessionId); } @@ -134,7 +138,7 @@ public abstract class MediaRoute2ProviderService extends Service { */ public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); if (sessionInfo.getSelectedRoutes().isEmpty()) { releaseSession(sessionId); return; @@ -160,7 +164,7 @@ public abstract class MediaRoute2ProviderService extends Service { public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { mSessionInfo.put(sessionId, sessionInfo); @@ -185,7 +189,7 @@ public abstract class MediaRoute2ProviderService extends Service { * controlled, pass a {@link Bundle} that contains how to control it. * * @param sessionInfo information of the new session. - * The {@link RouteSessionInfo#getSessionId() id} of the session must be + * The {@link RouteSessionInfo#getId() id} of the session must be * unique. Pass {@code null} to reject the request or inform clients that * session creation is failed. * @param requestId id of the previous request to create this session @@ -194,13 +198,13 @@ public abstract class MediaRoute2ProviderService extends Service { // TODO: Maybe better to create notifySessionCreationFailed? public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { if (sessionInfo != null) { - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { Log.w(TAG, "Ignoring duplicate session id."); return; } - mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo); + mSessionInfo.put(sessionInfo.getId(), sessionInfo); } schedulePublishState(); } @@ -220,9 +224,12 @@ public abstract class MediaRoute2ProviderService extends Service { * {@link #onDestroySession} is called if the session is released. * * @param sessionId id of the session to be released - * @see #onDestroySession(int, RouteSessionInfo) + * @see #onDestroySession(String, RouteSessionInfo) */ - public final void releaseSession(int sessionId) { + public final void releaseSession(@NonNull String sessionId) { + if (TextUtils.isEmpty(sessionId)) { + throw new IllegalArgumentException("sessionId must not be empty"); + } //TODO: notify media router service of release. RouteSessionInfo sessionInfo; synchronized (mSessionLock) { @@ -259,9 +266,10 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param sessionId id of the session being destroyed. * @param lastSessionInfo information of the session being destroyed. - * @see #releaseSession(int) + * @see #releaseSession(String) */ - public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo); + public abstract void onDestroySession(@NonNull String sessionId, + @NonNull RouteSessionInfo lastSessionInfo); //TODO: make a way to reject the request /** @@ -274,7 +282,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param routeId id of the route * @see #updateSessionInfo(RouteSessionInfo) */ - public abstract void onSelectRoute(int sessionId, @NonNull String routeId); + public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** @@ -286,7 +294,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session * @param routeId id of the route */ - public abstract void onDeselectRoute(int sessionId, @NonNull String routeId); + public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** @@ -298,7 +306,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session * @param routeId id of the route */ - public abstract void onTransferToRoute(int sessionId, @NonNull String routeId); + public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId); /** * Called when the {@link RouteDiscoveryRequest discovery request} has changed. @@ -385,37 +393,53 @@ public abstract class MediaRoute2ProviderService extends Service { requestId)); } @Override - public void releaseSession(int sessionId) { + public void releaseSession(@NonNull String sessionId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession, MediaRoute2ProviderService.this, sessionId)); } @Override - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } @Override - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } @Override - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index dea8b045e72a..8ebf6174cabf 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -342,8 +342,7 @@ public class MediaRouter2 { final int requestId; requestId = mSessionCreationRequestCnt.getAndIncrement(); - SessionCreationRequest request = new SessionCreationRequest( - requestId, route, routeType); + SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType); mSessionCreationRequests.add(request); Client2 client; @@ -352,8 +351,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.requestCreateSession( - client, route, routeType, requestId); + mMediaRouterService.requestCreateSession(client, route, routeType, requestId); } catch (RemoteException ex) { Log.e(TAG, "Unable to request to create session.", ex); mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, @@ -542,7 +540,7 @@ public class MediaRouter2 { if (sessionInfo != null) { RouteSessionController controller = new RouteSessionController(sessionInfo); synchronized (sRouterLock) { - mSessionControllers.put(controller.getUniqueSessionId(), controller); + mSessionControllers.put(controller.getSessionId(), controller); } notifySessionCreated(controller); } @@ -556,12 +554,12 @@ public class MediaRouter2 { RouteSessionController matchingController; synchronized (sRouterLock) { - matchingController = mSessionControllers.get(sessionInfo.getUniqueSessionId()); + matchingController = mSessionControllers.get(sessionInfo.getId()); } if (matchingController == null) { Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId=" - + sessionInfo.getUniqueSessionId()); + + sessionInfo.getId()); return; } @@ -582,7 +580,7 @@ public class MediaRouter2 { return; } - final String uniqueSessionId = sessionInfo.getUniqueSessionId(); + final String uniqueSessionId = sessionInfo.getId(); RouteSessionController matchingController; synchronized (sRouterLock) { matchingController = mSessionControllers.get(uniqueSessionId); @@ -591,7 +589,7 @@ public class MediaRouter2 { if (matchingController == null) { if (DEBUG) { Log.d(TAG, "releaseControllerOnHandler: Matching controller not found. " - + "uniqueSessionId=" + sessionInfo.getUniqueSessionId()); + + "uniqueSessionId=" + sessionInfo.getId()); } return; } @@ -783,20 +781,9 @@ public class MediaRouter2 { /** * @return the ID of the session */ - public int getSessionId() { + public String getSessionId() { synchronized (mControllerLock) { - return mSessionInfo.getSessionId(); - } - } - - /** - * @return the unique ID of the session - * @hide - */ - @NonNull - public String getUniqueSessionId() { - synchronized (mControllerLock) { - return mSessionInfo.getUniqueSessionId(); + return mSessionInfo.getId(); } } @@ -913,7 +900,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.selectRoute(client, getUniqueSessionId(), route); + mMediaRouterService.selectRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to select route for session.", ex); } @@ -960,7 +947,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.deselectRoute(client, getUniqueSessionId(), route); + mMediaRouterService.deselectRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to remove route from session.", ex); } @@ -1008,7 +995,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.transferToRoute(client, getUniqueSessionId(), route); + mMediaRouterService.transferToRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to transfer to route for session.", ex); } @@ -1033,12 +1020,12 @@ public class MediaRouter2 { Client2 client; synchronized (sRouterLock) { - mSessionControllers.remove(getUniqueSessionId(), this); + mSessionControllers.remove(getSessionId(), this); client = mClient; } if (client != null) { try { - mMediaRouterService.releaseSession(client, getUniqueSessionId()); + mMediaRouterService.releaseSession(client, getSessionId()); } catch (RemoteException ex) { Log.e(TAG, "Unable to notify of controller release", ex); } @@ -1068,6 +1055,7 @@ public class MediaRouter2 { List<MediaRoute2Info> routes = new ArrayList<>(); synchronized (sRouterLock) { + // TODO: Maybe able to change using Collection.stream()? for (String routeId : routeIds) { MediaRoute2Info route = mRoutes.get(routeId); if (route != null) { diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java new file mode 100644 index 000000000000..49045828dbe8 --- /dev/null +++ b/media/java/android/media/MediaRouter2Utils.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +/** + * @hide + */ +public class MediaRouter2Utils { + + static final String TAG = "MR2Utils"; + static final String SEPARATOR = ":"; + + /** + * @hide + */ + @NonNull + public static String toUniqueId(@NonNull String providerId, @NonNull String id) { + if (TextUtils.isEmpty(providerId)) { + Log.w(TAG, "toUniqueId: providerId shouldn't be empty"); + return null; + } + if (TextUtils.isEmpty(id)) { + Log.w(TAG, "toUniqueId: id shouldn't be null"); + return null; + } + + return providerId + SEPARATOR + id; + } + + /** + * Gets provider ID from unique ID. + * If the corresponding provider ID could not be generated, it will return null. + * + * @hide + */ + @Nullable + public static String getProviderId(@NonNull String uniqueId) { + if (TextUtils.isEmpty(uniqueId)) { + Log.w(TAG, "getProviderId: uniqueId shouldn't be empty"); + return null; + } + + int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR); + if (firstIndexOfSeparator == -1) { + return null; + } + + String providerId = uniqueId.substring(0, firstIndexOfSeparator); + if (TextUtils.isEmpty(providerId)) { + return null; + } + + return providerId; + } + + /** + * Gets the original ID (i.e. non-unique route/session ID) from unique ID. + * If the corresponding ID could not be generated, it will return null. + * + * @hide + */ + @Nullable + public static String getOriginalId(@NonNull String uniqueId) { + if (TextUtils.isEmpty(uniqueId)) { + Log.w(TAG, "getOriginalId: uniqueId shouldn't be empty"); + return null; + } + + int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR); + if (firstIndexOfSeparator == -1 || firstIndexOfSeparator + 1 >= uniqueId.length()) { + return null; + } + + String providerId = uniqueId.substring(firstIndexOfSeparator + 1); + if (TextUtils.isEmpty(providerId)) { + return null; + } + + return providerId; + } +} diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java index cb1688600fac..5330630ef3a9 100644 --- a/media/java/android/media/RouteSessionInfo.java +++ b/media/java/android/media/RouteSessionInfo.java @@ -22,6 +22,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Log; import java.util.ArrayList; import java.util.Collections; @@ -33,6 +34,7 @@ import java.util.Objects; * @hide */ public class RouteSessionInfo implements Parcelable { + @NonNull public static final Creator<RouteSessionInfo> CREATOR = new Creator<RouteSessionInfo>() { @@ -46,8 +48,10 @@ public class RouteSessionInfo implements Parcelable { } }; - final int mSessionId; - final String mPackageName; + public static final String TAG = "RouteSessionInfo"; + + final String mId; + final String mClientPackageName; final String mRouteType; @Nullable final String mProviderId; @@ -61,15 +65,19 @@ public class RouteSessionInfo implements Parcelable { RouteSessionInfo(@NonNull Builder builder) { Objects.requireNonNull(builder, "builder must not be null."); - mSessionId = builder.mSessionId; - mPackageName = builder.mPackageName; + mId = builder.mId; + mClientPackageName = builder.mClientPackageName; mRouteType = builder.mRouteType; mProviderId = builder.mProviderId; - mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes); - mSelectableRoutes = Collections.unmodifiableList(builder.mSelectableRoutes); - mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes); - mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes); + mSelectedRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mSelectedRoutes)); + mSelectableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mSelectableRoutes)); + mDeselectableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mDeselectableRoutes)); + mTransferrableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mTransferrableRoutes)); mControlHints = builder.mControlHints; } @@ -77,8 +85,8 @@ public class RouteSessionInfo implements Parcelable { RouteSessionInfo(@NonNull Parcel src) { Objects.requireNonNull(src, "src must not be null."); - mSessionId = src.readInt(); - mPackageName = ensureString(src.readString()); + mId = ensureString(src.readString()); + mClientPackageName = ensureString(src.readString()); mRouteType = ensureString(src.readString()); mProviderId = src.readString(); @@ -105,73 +113,50 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets non-unique session id (int) from unique session id (string). - * If the corresponding session id could not be generated, it will return null. - * @hide + * Returns whether the session info is valid or not + * + * TODO in this CL: Remove this method. */ - @Nullable - public static Integer getSessionId(@NonNull String uniqueSessionId) { - int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/"); - if (lastIndexOfSeparator == -1 || lastIndexOfSeparator + 1 >= uniqueSessionId.length()) { - return null; - } - - String integerString = uniqueSessionId.substring(lastIndexOfSeparator + 1); - if (TextUtils.isEmpty(integerString)) { - return null; - } - - try { - return Integer.parseInt(integerString); - } catch (NumberFormatException ex) { - return null; - } + public boolean isValid() { + return !TextUtils.isEmpty(mId) + && !TextUtils.isEmpty(mClientPackageName) + && !TextUtils.isEmpty(mRouteType) + && mSelectedRoutes.size() > 0; } /** - * Gets provider ID (string) from unique session id (string). - * If the corresponding provider ID could not be generated, it will return null. - * @hide + * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have + * unique IDs. + * <p> + * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method + * can be different from what was set in {@link MediaRoute2ProviderService}. * - * TODO: This logic seems error-prone. Consider to use long uniqueId. + * @see Builder#Builder(String, String, String) */ - @Nullable - public static String getProviderId(@NonNull String uniqueSessionId) { - int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/"); - if (lastIndexOfSeparator == -1) { - return null; - } - - String result = uniqueSessionId.substring(0, lastIndexOfSeparator); - if (TextUtils.isEmpty(result)) { - return null; + @NonNull + public String getId() { + if (mProviderId != null) { + return MediaRouter2Utils.toUniqueId(mProviderId, mId); + } else { + return mId; } - return result; - } - - /** - * Returns whether the session info is valid or not - */ - public boolean isValid() { - return !TextUtils.isEmpty(mPackageName) - && !TextUtils.isEmpty(mRouteType) - && mSelectedRoutes.size() > 0; } /** - * Gets the id of the session + * Gets the original id set by {@link Builder#Builder(String, String, String)}. + * @hide */ @NonNull - public int getSessionId() { - return mSessionId; + public String getOriginalId() { + return mId; } /** * Gets the client package name of the session */ @NonNull - public String getPackageName() { - return mPackageName; + public String getClientPackageName() { + return mClientPackageName; } /** @@ -193,19 +178,6 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets the unique id of the session. - * @hide - */ - @NonNull - public String getUniqueSessionId() { - StringBuilder sessionIdBuilder = new StringBuilder() - .append(mProviderId) - .append("/") - .append(mSessionId); - return sessionIdBuilder.toString(); - } - - /** * Gets the list of ids of selected routes for the session. It shouldn't be empty. */ @NonNull @@ -252,8 +224,8 @@ public class RouteSessionInfo implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mSessionId); - dest.writeString(mPackageName); + dest.writeString(mId); + dest.writeString(mClientPackageName); dest.writeString(mRouteType); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); @@ -267,7 +239,7 @@ public class RouteSessionInfo implements Parcelable { public String toString() { StringBuilder result = new StringBuilder() .append("RouteSessionInfo{ ") - .append("sessionId=").append(mSessionId) + .append("sessionId=").append(mId) .append(", routeType=").append(mRouteType) .append(", selectedRoutes={") .append(String.join(",", mSelectedRoutes)) @@ -285,12 +257,30 @@ public class RouteSessionInfo implements Parcelable { return result.toString(); } + private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) { + if (routeIds == null) { + Log.w(TAG, "routeIds is null. Returning an empty list"); + return Collections.emptyList(); + } + + // mProviderId can be null if not set. Return the original list for this case. + if (mProviderId == null) { + return routeIds; + } + + List<String> result = new ArrayList<>(); + for (String routeId : routeIds) { + result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId)); + } + return result; + } + /** * Builder class for {@link RouteSessionInfo}. */ public static final class Builder { - final String mPackageName; - final int mSessionId; + final String mId; + final String mClientPackageName; final String mRouteType; String mProviderId; final List<String> mSelectedRoutes; @@ -299,22 +289,42 @@ public class RouteSessionInfo implements Parcelable { final List<String> mTransferrableRoutes; Bundle mControlHints; - public Builder(int sessionId, @NonNull String packageName, + /** + * Constructor for builder to create {@link RouteSessionInfo}. + * <p> + * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of + * {@link RouteSessionInfo#getId()} can be different from what was set in + * {@link MediaRoute2ProviderService}. + * </p> + * + * @see MediaRoute2Info#getId() + */ + public Builder(@NonNull String id, @NonNull String clientPackageName, @NonNull String routeType) { - mSessionId = sessionId; - mPackageName = Objects.requireNonNull(packageName, "packageName must not be null"); - mRouteType = Objects.requireNonNull(routeType, - "routeType must not be null"); - + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must not be empty"); + } + mId = id; + mClientPackageName = Objects.requireNonNull( + clientPackageName, "clientPackageName must not be null"); + mRouteType = Objects.requireNonNull(routeType, "routeType must not be null"); mSelectedRoutes = new ArrayList<>(); mSelectableRoutes = new ArrayList<>(); mDeselectableRoutes = new ArrayList<>(); mTransferrableRoutes = new ArrayList<>(); } - public Builder(RouteSessionInfo sessionInfo) { - mSessionId = sessionInfo.mSessionId; - mPackageName = sessionInfo.mPackageName; + /** + * Constructor for builder to create {@link RouteSessionInfo} with + * existing {@link RouteSessionInfo} instance. + * + * @param sessionInfo the existing instance to copy data from. + */ + public Builder(@NonNull RouteSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + mId = sessionInfo.mId; + mClientPackageName = sessionInfo.mClientPackageName; mRouteType = sessionInfo.mRouteType; mProviderId = sessionInfo.mProviderId; @@ -334,23 +344,14 @@ public class RouteSessionInfo implements Parcelable { * @hide */ @NonNull - public Builder setProviderId(String providerId) { + public Builder setProviderId(@NonNull String providerId) { + if (TextUtils.isEmpty(providerId)) { + throw new IllegalArgumentException("providerId must not be empty"); + } mProviderId = providerId; - convertToUniqueRouteIds(providerId, mSelectedRoutes); - convertToUniqueRouteIds(providerId, mSelectableRoutes); - convertToUniqueRouteIds(providerId, mDeselectableRoutes); - convertToUniqueRouteIds(providerId, mTransferrableRoutes); return this; } - private void convertToUniqueRouteIds(@NonNull String providerId, - @NonNull List<String> routeIds) { - for (int i = 0; i < routeIds.size(); i++) { - String routeId = routeIds.get(i); - routeIds.set(i, MediaRoute2Info.toUniqueId(providerId, routeId)); - } - } - /** * Clears the selected routes. */ diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index aff725734ee1..aece39d78694 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -940,16 +940,15 @@ public final class MediaSessionManager { /** * Called when a media key event is dispatched through the media session service. The * session token can be {@link null} if the framework has sent the media key event to the - * media button receiver to revive the media app's playback. - * - * the session is dead when , but the framework sent + * media button receiver to revive the media app's playback after the corresponding session + * is released. * * @param event Dispatched media key event. * @param packageName Package * @param sessionToken The media session's token. Can be {@code null}. */ default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName, - @NonNull MediaSession.Token sessionToken) { } + @Nullable MediaSession.Token sessionToken) { } } /** diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 1c38301c7935..938ffcd5f731 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -428,8 +428,7 @@ public final class SoundTriggerManager { */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable UUID soundModelId, - @ModelParams int modelParam, int value) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam, int value) { try { return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam, value); @@ -449,15 +448,10 @@ public final class SoundTriggerManager { * @param soundModelId UUID of model to get parameter * @param modelParam {@link ModelParams} * @return value of parameter - * @throws UnsupportedOperationException if hal or model do not support this API. - * {@link SoundTriggerManager#queryParameter} should be checked first. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - * {@link SoundTriggerManager#queryParameter} should be checked first. */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull UUID soundModelId, - @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam) { try { return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { @@ -479,8 +473,7 @@ public final class SoundTriggerManager { public ModelParamRange queryParameter(@Nullable UUID soundModelId, @ModelParams int modelParam) { try { - return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), - modelParam); + return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl index 1a3b40261a62..909f1a00006f 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl +++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl @@ -30,6 +30,14 @@ parcelable SoundTriggerModuleProperties { * Unique implementation ID. The UUID must change with each version of the engine implementation */ String uuid; + /** + * String naming the architecture used for running the supported models. + * (eg. a platform running models on a DSP could implement this string to convey the DSP + * architecture used) + * This property is supported for soundtrigger HAL v2.3 and above. + * If running a previous version, the string will be empty. + */ + String supportedModelArch; /** Maximum number of concurrent sound models loaded */ int maxSoundModels; /** Maximum number of key phrases */ diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl index d8f9d8f7e891..5d082e272295 100644 --- a/media/java/android/media/soundtrigger_middleware/Status.aidl +++ b/media/java/android/media/soundtrigger_middleware/Status.aidl @@ -26,4 +26,6 @@ enum Status { RESOURCE_CONTENTION = 1, /** Operation is not supported in this implementation. This is a permanent condition. */ OPERATION_NOT_SUPPORTED = 2, + /** Temporary lack of permission. */ + TEMPORARY_PERMISSION_DENIED = 3, } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 7057879da764..f8b46b305216 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -23,9 +23,9 @@ import android.annotation.SystemApi; import android.content.Context; import android.media.tv.tuner.TunerConstants.FilterStatus; import android.media.tv.tuner.TunerConstants.FilterSubtype; -import android.media.tv.tuner.TunerConstants.FilterType; import android.media.tv.tuner.TunerConstants.FrontendScanType; import android.media.tv.tuner.TunerConstants.Result; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; import android.media.tv.tuner.filter.FilterEvent; import android.media.tv.tuner.frontend.FrontendCallback; import android.media.tv.tuner.frontend.FrontendInfo; diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index 41b372e7cd25..203eff8187b2 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -128,21 +128,6 @@ public final class TunerConstants { public static final int FRONTEND_SETTINGS_ISDBT = 9; /** @hide */ - @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) - @Retention(RetentionPolicy.SOURCE) - public @interface FilterType {} - /** @hide */ - public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS; - /** @hide */ - public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP; - /** @hide */ - public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP; - /** @hide */ - public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV; - /** @hide */ - public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; - - /** @hide */ @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES, FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD, FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI, diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java index a7ccb0288e03..8780b726e3b2 100644 --- a/media/java/android/media/tv/tuner/TunerUtils.java +++ b/media/java/android/media/tv/tuner/TunerUtils.java @@ -20,7 +20,8 @@ import android.content.Context; import android.content.pm.PackageManager; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.TunerConstants.FilterSubtype; -import android.media.tv.tuner.TunerConstants.FilterType; +import android.media.tv.tuner.filter.FilterConfiguration; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** * Utility class for tuner framework. @@ -50,7 +51,7 @@ public final class TunerUtils { * @param subtype filter subtype. */ public static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) { - if (mainType == TunerConstants.FILTER_TYPE_TS) { + if (mainType == FilterConfiguration.FILTER_TYPE_TS) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxTsFilterType.UNDEFINED; @@ -73,7 +74,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_MMTP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxMmtpFilterType.UNDEFINED; @@ -95,7 +96,7 @@ public final class TunerUtils { break; } - } else if (mainType == TunerConstants.FILTER_TYPE_IP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_IP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxIpFilterType.UNDEFINED; @@ -112,7 +113,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_TLV) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_TLV) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxTlvFilterType.UNDEFINED; @@ -125,7 +126,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_ALP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_ALP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxAlpFilterType.UNDEFINED; diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java index 4d02b849899d..f0fe533093ba 100644 --- a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a ALP filter. * @hide @@ -32,6 +30,6 @@ public class AlpFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_ALP; + return FilterConfiguration.FILTER_TYPE_ALP; } } diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java index 930ca744a168..99b10cde34f9 100644 --- a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java @@ -16,28 +16,62 @@ package android.media.tv.tuner.filter; +import android.annotation.IntDef; import android.annotation.Nullable; -import android.media.tv.tuner.TunerConstants.FilterType; +import android.hardware.tv.tuner.V1_0.Constants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** - * Demux Filter configuration. + * Filter configuration used to configure filters. * * @hide */ public abstract class FilterConfiguration { + + /** @hide */ + @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterType {} + + /** + * TS filter type. + */ + public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS; + /** + * MMTP filter type. + */ + public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP; + /** + * IP filter type. + */ + public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP; + /** + * TLV filter type. + */ + public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV; + /** + * ALP filter type. + */ + public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; + @Nullable - protected final Settings mSettings; + private final Settings mSettings; - protected FilterConfiguration(Settings settings) { + /* package */ FilterConfiguration(Settings settings) { mSettings = settings; } /** - * Gets filter configuration type + * Gets filter configuration type. + * @hide */ @FilterType public abstract int getType(); + /** @hide */ + @Nullable public Settings getSettings() { return mSettings; } diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java index 2c706c0ce9ce..c89636887628 100644 --- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a IP filter. * @hide @@ -35,6 +33,6 @@ public class IpFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_IP; + return FilterConfiguration.FILTER_TYPE_IP; } } diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java index f70e70a5bdb2..9045ce67a61f 100644 --- a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a MMTP filter. * @hide @@ -31,6 +29,6 @@ public class MmtpFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_MMTP; + return FilterConfiguration.FILTER_TYPE_MMTP; } } diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java index 3d2c265cc00b..f38abf12e120 100644 --- a/media/java/android/media/tv/tuner/filter/PesSettings.java +++ b/media/java/android/media/tv/tuner/filter/PesSettings.java @@ -16,45 +16,54 @@ package android.media.tv.tuner.filter; +import android.annotation.NonNull; import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** * Filter Settings for a PES Data. + * * @hide */ public class PesSettings extends Settings { - private int mStreamId; - private boolean mIsRaw; + private final int mStreamId; + private final boolean mIsRaw; - private PesSettings(int mainType, int streamId, boolean isRaw) { + private PesSettings(@FilterType int mainType, int streamId, boolean isRaw) { super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES)); mStreamId = streamId; mIsRaw = isRaw; } /** - * Creates a builder for PesSettings. + * Creates a builder for {@link PesSettings}. + * + * @param mainType the filter main type of the settings. */ - public static Builder newBuilder(int mainType) { + @NonNull + public static Builder newBuilder(@FilterType int mainType) { return new Builder(mainType); } /** - * Builder for PesSettings. + * Builder for {@link PesSettings}. */ public static class Builder { private final int mMainType; private int mStreamId; private boolean mIsRaw; - public Builder(int mainType) { + private Builder(int mainType) { mMainType = mainType; } /** * Sets stream ID. + * + * @param streamId the stream ID. */ + @NonNull public Builder setStreamId(int streamId) { mStreamId = streamId; return this; @@ -62,16 +71,20 @@ public class PesSettings extends Settings { /** * Sets whether it's raw. - * true if the filter send onFilterStatus instead of onFilterEvent. + * + * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback + * instead of onFilterEvent for raw data. {@code false} otherwise. */ + @NonNull public Builder setIsRaw(boolean isRaw) { mIsRaw = isRaw; return this; } /** - * Builds a PesSettings instance. + * Builds a {@link PesSettings} object. */ + @NonNull public PesSettings build() { return new PesSettings(mMainType, mStreamId, mIsRaw); } diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java index 789ed0895750..146aca74ce0e 100644 --- a/media/java/android/media/tv/tuner/filter/Settings.java +++ b/media/java/android/media/tv/tuner/filter/Settings.java @@ -18,18 +18,20 @@ package android.media.tv.tuner.filter; /** * Settings for filters of different subtypes. + * * @hide */ public abstract class Settings { - protected final int mType; + private final int mType; - protected Settings(int type) { + /* package */ Settings(int type) { mType = type; } /** * Gets filter settings type. - * @return + * + * @hide */ public int getType() { return mType; diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java index 1f8bcb3b4a71..de8ee754a28c 100644 --- a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java @@ -16,8 +16,6 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; - /** * Filter configuration for a TLV filter. * @hide @@ -33,6 +31,6 @@ public class TlvFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_TLV; + return FilterConfiguration.FILTER_TYPE_TLV; } } diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java index 103698cd0d75..d0241b6aba09 100644 --- a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java +++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java @@ -16,14 +16,15 @@ package android.media.tv.tuner.filter; -import android.media.tv.tuner.TunerConstants; +import android.annotation.NonNull; /** * Filter configuration for a TS filter. + * * @hide */ public class TsFilterConfiguration extends FilterConfiguration { - private int mTpid; + private final int mTpid; private TsFilterConfiguration(Settings settings, int tpid) { super(settings); @@ -32,42 +33,50 @@ public class TsFilterConfiguration extends FilterConfiguration { @Override public int getType() { - return TunerConstants.FILTER_TYPE_TS; + return FilterConfiguration.FILTER_TYPE_TS; } /** - * Creates a new builder. + * Creates a builder for {@link TsFilterConfiguration}. */ - public static TsFilterConfiguration.Builder newBuilder() { - return new TsFilterConfiguration.Builder(); + @NonNull + public static Builder newBuilder() { + return new Builder(); } /** - * Builder for TsFilterConfiguration. + * Builder for {@link TsFilterConfiguration}. */ public static class Builder { private Settings mSettings; private int mTpid; /** - * Sets settings. + * Sets filter settings. + * + * @param settings the filter settings. */ - public TsFilterConfiguration.Builder setSettings(Settings settings) { + @NonNull + public Builder setSettings(@NonNull Settings settings) { mSettings = settings; return this; } /** - * Sets TPID. + * Sets Tag Protocol ID. + * + * @param tpid the Tag Protocol ID. */ - public TsFilterConfiguration.Builder setTpid(int tpid) { + @NonNull + public Builder setTpid(int tpid) { mTpid = tpid; return this; } /** - * Builds a TsFilterConfiguration instance. + * Builds a {@link TsFilterConfiguration} object. */ + @NonNull public TsFilterConfiguration build() { return new TsFilterConfiguration(mSettings, mTpid); } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index ee6761344613..536a061190d7 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -143,6 +143,10 @@ cc_library_shared { "libutils", ], + header_libs: [ + "libstagefright_foundation_headers", + ], + export_include_dirs: ["."], cflags: [ diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index ec177321bba3..8c0273b06e8c 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -64,7 +64,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService "com.android.mediarouteprovider.TYPE_SPECIAL"; Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); - Map<String, Integer> mRouteSessionMap = new HashMap<>(); + Map<String, String> mRouteSessionMap = new HashMap<>(); private int mNextSessionId = 1000; private void initializeRoutes() { @@ -177,7 +177,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } maybeDeselectRoute(routeId); - final int sessionId = mNextSessionId; + final String sessionId = String.valueOf(mNextSessionId); mNextSessionId++; mRoutes.put(routeId, new MediaRoute2Info.Builder(route) @@ -196,7 +196,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) { + public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) { for (String routeId : lastSessionInfo.getSelectedRoutes()) { mRouteSessionMap.remove(routeId); MediaRoute2Info route = mRoutes.get(routeId); @@ -209,7 +209,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onSelectRoute(int sessionId, String routeId) { + public void onSelectRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); if (route == null || sessionInfo == null) { @@ -218,7 +218,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService maybeDeselectRoute(routeId); mRoutes.put(routeId, new MediaRoute2Info.Builder(route) - .setClientPackageName(sessionInfo.getPackageName()) + .setClientPackageName(sessionInfo.getClientPackageName()) .build()); mRouteSessionMap.put(routeId, sessionId); @@ -232,7 +232,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDeselectRoute(int sessionId, String routeId) { + public void onDeselectRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); @@ -254,7 +254,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onTransferToRoute(int sessionId, String routeId) { + public void onTransferToRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) .clearSelectedRoutes() @@ -271,7 +271,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService return; } - int sessionId = mRouteSessionMap.get(routeId); + String sessionId = mRouteSessionMap.get(routeId); onDeselectRoute(sessionId, routeId); } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java index af69c7e8699f..ce4bb8ef2688 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java @@ -400,6 +400,7 @@ public class MediaRouter2Test { assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2)); assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType())); assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType())); + } finally { releaseControllers(createdControllers); mRouter2.unregisterRouteCallback(routeCallback); @@ -486,20 +487,21 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } if (onSessionInfoChangedLatchForSelect.getCount() != 0) { // Check oldInfo - assertEquals(controller.getSessionId(), oldInfo.getSessionId()); + assertEquals(controller.getSessionId(), oldInfo.getId()); assertEquals(1, oldInfo.getSelectedRoutes().size()); assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(oldInfo.getSelectableRoutes().contains( ROUTE_ID4_TO_SELECT_AND_DESELECT)); // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(2, newInfo.getSelectedRoutes().size()); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(newInfo.getSelectedRoutes().contains( @@ -510,7 +512,7 @@ public class MediaRouter2Test { onSessionInfoChangedLatchForSelect.countDown(); } else { // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(1, newInfo.getSelectedRoutes().size()); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertFalse(newInfo.getSelectedRoutes().contains( @@ -587,18 +589,19 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } // Check oldInfo - assertEquals(controller.getSessionId(), oldInfo.getSessionId()); + assertEquals(controller.getSessionId(), oldInfo.getId()); assertEquals(1, oldInfo.getSelectedRoutes().size()); assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO)); // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(1, newInfo.getSelectedRoutes().size()); assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO)); @@ -665,7 +668,8 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } onSessionInfoChangedLatch.countDown(); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index c23a5b0d76e3..9ff9177c1b40 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -99,7 +99,7 @@ public class MediaRouterManagerTest { public static final String TYPE_SPECIAL = "com.android.mediarouteprovider.TYPE_SPECIAL"; - private static final String TYPE_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO"; + private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; private static final int TIMEOUT_MS = 5000; diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java index 2e81a646b0db..9971fc3bbe9f 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java @@ -29,6 +29,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class RouteSessionTest { + private static final String TEST_SESSION_ID = "test_session_id"; private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest"; private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category"; @@ -36,17 +37,17 @@ public class RouteSessionTest { @Test public void testValidity() { - RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, "", TEST_CONTROL_CATEGORY) .addSelectedRoute(TEST_ROUTE_ID1) .build(); - RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME, "") .addSelectedRoute(TEST_ROUTE_ID1) .build(); - RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY) .build(); @@ -54,9 +55,9 @@ public class RouteSessionTest { .addSelectedRoute(TEST_ROUTE_ID1) .build(); - assertFalse(emptySelectedRouteSession.isValid()); assertFalse(emptyPackageSession.isValid()); assertFalse(emptyCategorySession.isValid()); + assertFalse(emptySelectedRouteSession.isValid()); assertTrue(validSession.isValid()); } } diff --git a/mms/OWNERS b/mms/OWNERS index ba00d5d75010..befc320b949c 100644 --- a/mms/OWNERS +++ b/mms/OWNERS @@ -12,3 +12,5 @@ satk@google.com shuoq@google.com refuhoo@google.com nazaninb@google.com +sarahchin@google.com +dbright@google.com
\ No newline at end of file diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 1ee85188eac3..3c3ebe2fe228 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -110,7 +110,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; @@ -277,7 +276,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -365,7 +363,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt notificationGutsManager, notificationLogger, notificationEntryManager, - notificationRowContentBinder, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 7108e65c8bce..a1eccceea771 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -70,7 +70,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; @@ -147,7 +146,6 @@ public class CarStatusBarModule { NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -234,7 +232,6 @@ public class CarStatusBarModule { notificationGutsManager, notificationLogger, notificationEntryManager, - notificationRowContentBinder, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index a2bd210b67a6..a784e04ee6a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -148,17 +151,18 @@ public class A2dpProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService == null) { + return false; } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -182,12 +186,12 @@ public class A2dpProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -197,11 +201,11 @@ public class A2dpProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } boolean isA2dpPlaying() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index bc03c343a909..8ca5a74652dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothA2dpSink; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -116,18 +119,15 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -141,12 +141,12 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -156,11 +156,11 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 560cb3b9b5b4..d65b5da22056 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -112,18 +115,15 @@ public class HeadsetProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -165,12 +165,12 @@ public class HeadsetProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -180,11 +180,11 @@ public class HeadsetProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index b4b55f363020..9f1af669c708 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -146,17 +149,18 @@ public class HearingAidProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - // Downgrade priority as user is disconnecting the hearing aid. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService == null) { + return false; } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -180,12 +184,12 @@ public class HearingAidProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -195,11 +199,11 @@ public class HearingAidProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index a372e23654e0..678f2e37c6bf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -126,7 +129,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } @Override @@ -134,11 +137,8 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } @Override @@ -154,13 +154,13 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } @Override public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -171,11 +171,11 @@ final class HfpClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 975a1e67af5b..588083e73481 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -99,13 +102,17 @@ public class HidProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -119,12 +126,12 @@ public class HidProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -132,11 +139,11 @@ public class HidProfile implements LocalBluetoothProfile { public void setPreferred(BluetoothDevice device, boolean preferred) { if (mService == null) return; if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 95139a1bfab9..7d121aaa1ad1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -115,18 +118,15 @@ public final class MapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -140,12 +140,12 @@ public final class MapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -155,11 +155,11 @@ public final class MapClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index 31a0eea56b42..a96a4e73feea 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -119,10 +121,8 @@ public class MapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -136,12 +136,12 @@ public class MapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -155,7 +155,7 @@ public class MapProfile implements LocalBluetoothProfile { mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 4ea0df621bea..56267fc596cf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -129,7 +132,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return false; } Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress()); - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { @@ -137,7 +140,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.disconnect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -151,12 +154,12 @@ public final class PbapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -166,11 +169,11 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index 3f920a8cf1dd..f7c0bf5c8c9d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -96,8 +98,10 @@ public class PbapServerProfile implements LocalBluetoothProfile { } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 0ca4d6195a32..3022c5b566eb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -112,17 +115,15 @@ final class SapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -136,12 +137,12 @@ final class SapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -151,11 +152,11 @@ final class SapProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java index 976445eb8c04..ccb6646cf683 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class A2dpSinkProfileTest { @Test public void connect_shouldConnectBluetoothA2dpSink() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothA2dpSink() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java index 69c020dd5c08..91807609df1a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class HfpClientProfileTest { @Test public void connect_shouldConnectBluetoothHeadsetClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothHeadsetClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java index 6f667094a5aa..1425c381256b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class MapClientProfileTest { @Test public void connect_shouldConnectBluetoothMapClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothMapClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java index b21ec9c3e52a..15f560bef73e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class PbapClientProfileTest { @Test public void connect_shouldConnectBluetoothPbapClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothPbapClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java index ec880345f6f0..4f978a822890 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -66,13 +69,13 @@ public class SapProfileTest { @Test public void connect_shouldConnectBluetoothSap() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothSap() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 347d6c2264eb..1c63efc3b5bc 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -194,6 +194,9 @@ <uses-permission android:name="android.permission.MANAGE_APPOPS" /> + <!-- Permission required for storage tests - FuseDaemonHostTest --> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> + <!-- Permission needed to run network tests in CTS --> <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" /> <!-- Permission needed to test tcp keepalive offload. --> diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md index 56a637fe588c..28657f28e53b 100644 --- a/packages/SystemUI/docs/broadcasts.md +++ b/packages/SystemUI/docs/broadcasts.md @@ -42,24 +42,29 @@ Acquire the dispatcher by using `@Inject` to obtain a `BroadcastDispatcher`. The ```kotlin /** - * Register a receiver for broadcast with the dispatcher - * - * @param receiver A receiver to dispatch the [Intent] - * @param filter A filter to determine what broadcasts should be dispatched to this receiver. - * It will only take into account actions and categories for filtering. It must - * have at least one action. - * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the - * main handler. Pass `null` to use the default. - * @param user A user handle to determine which broadcast should be dispatched to this receiver. - * By default, it is the current user. - * @throws IllegalArgumentException if the filter has other constraints that are not actions or - * categories or the filter has no actions. - */ + * Register a receiver for broadcast with the dispatcher + * + * @param receiver A receiver to dispatch the [Intent] + * @param filter A filter to determine what broadcasts should be dispatched to this receiver. + * It will only take into account actions and categories for filtering. It must + * have at least one action. + * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an + * executor in the main thread (default). + * @param user A user handle to determine which broadcast should be dispatched to this receiver. + * By default, it is the current user. + * @throws IllegalArgumentException if the filter has other constraints that are not actions or + * categories or the filter has no actions. + */ @JvmOverloads -fun registerReceiver(BroadcastReceiver, IntentFilter, Handler? = mainHandler, UserHandle = context.user) +fun registerReceiver( + BroadcastReceiver, + IntentFilter, + Executor? = context.mainExecutor, + UserHandle = context.user +) { ``` -All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Handler`, pass `null` for the `Handler`. +All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Executor`, pass `null` for the `Executor`. In the same way as with `Context`, subscribing the same `BroadcastReceiver` for the same user using different filters will result on two subscriptions, not in replacing the filter. diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml new file mode 100644 index 000000000000..e91f840fe238 --- /dev/null +++ b/packages/SystemUI/res/layout/media_carousel.xml @@ -0,0 +1,35 @@ +<?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 + --> + +<!-- Carousel for media controls --> +<HorizontalScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_height" + android:padding="@dimen/qs_media_padding" + android:scrollbars="none" + android:visibility="gone" + > + <LinearLayout + android:id="@+id/media_carousel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <!-- QSMediaPlayers will be added here dynamically --> + </LinearLayout> +</HorizontalScrollView> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 431862f1cc11..a58e3d78bb08 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1638,7 +1638,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); - broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mHandler); + broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler); final IntentFilter allUserFilter = new IntentFilter(); allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED); @@ -1649,8 +1649,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab allUserFilter.addAction(ACTION_USER_UNLOCKED); allUserFilter.addAction(ACTION_USER_STOPPED); allUserFilter.addAction(ACTION_USER_REMOVED); - broadcastDispatcher.registerReceiver(mBroadcastAllReceiver, allUserFilter, mHandler, - UserHandle.ALL); + broadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter, + mHandler, UserHandle.ALL); mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener); try { diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt index 8c7075bee6cc..f14c4cd8e6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/DumpController.kt +++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt @@ -30,6 +30,14 @@ import javax.inject.Singleton /** * Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI * dependencies. + * + * To dump a specific dumpable on-demand: + * + * ``` + * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService dependency DumpController <tag1>,<tag2>,<tag3> + * ``` + * + * Where tag1, tag2, etc. are the tags of the dumpables you want to dump. */ @Singleton class DumpController @Inject constructor() : Dumpable { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 0e736dcd11a8..c533755c76da 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -266,7 +266,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler); + mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler); mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 8cb0cc5db1d3..cedf7c354ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.IntentFilter import android.os.Handler +import android.os.HandlerExecutor import android.os.Looper import android.os.Message import android.os.UserHandle @@ -32,13 +33,14 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import java.io.FileDescriptor import java.io.PrintWriter +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton data class ReceiverData( val receiver: BroadcastReceiver, val filter: IntentFilter, - val handler: Handler, + val executor: Executor, val user: UserHandle ) @@ -76,24 +78,49 @@ open class BroadcastDispatcher @Inject constructor ( * @param filter A filter to determine what broadcasts should be dispatched to this receiver. * It will only take into account actions and categories for filtering. It must * have at least one action. - * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the - * main handler. Pass `null` to use the default. + * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. * @param user A user handle to determine which broadcast should be dispatched to this receiver. * By default, it is the current user. * @throws IllegalArgumentException if the filter has other constraints that are not actions or * categories or the filter has no actions. */ + @Deprecated(message = "Replacing Handler for Executor in SystemUI", + replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)")) @JvmOverloads - fun registerReceiver( + fun registerReceiverWithHandler( receiver: BroadcastReceiver, filter: IntentFilter, - handler: Handler? = mainHandler, + handler: Handler, user: UserHandle = context.user ) { + registerReceiver(receiver, filter, HandlerExecutor(handler), user) + } + + /** + * Register a receiver for broadcast with the dispatcher + * + * @param receiver A receiver to dispatch the [Intent] + * @param filter A filter to determine what broadcasts should be dispatched to this receiver. + * It will only take into account actions and categories for filtering. It must + * have at least one action. + * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an + * executor in the main thread (default). + * @param user A user handle to determine which broadcast should be dispatched to this receiver. + * By default, it is the current user. + * @throws IllegalArgumentException if the filter has other constraints that are not actions or + * categories or the filter has no actions. + */ + @JvmOverloads + fun registerReceiver( + receiver: BroadcastReceiver, + filter: IntentFilter, + executor: Executor? = context.mainExecutor, + user: UserHandle = context.user + ) { checkFilter(filter) this.handler .obtainMessage(MSG_ADD_RECEIVER, - ReceiverData(receiver, filter, handler ?: mainHandler, user)) + ReceiverData(receiver, filter, executor ?: context.mainExecutor, user)) .sendToTarget() } diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index b2942bb14c6b..0c631aacab82 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -193,7 +193,7 @@ class UserBroadcastDispatcher( it.filter.hasAction(intent.action) && it.filter.matchCategories(intent.categories) == null } ?.forEach { - it.handler.post { + it.executor.execute { if (DEBUG) Log.w(TAG, "[$index] Dispatching ${intent.action} to ${it.receiver}") it.receiver.pendingResult = pendingResult diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 97224f1234dd..ccbbb2465742 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -111,7 +111,10 @@ public class BubbleData { } private final Context mContext; + /** Bubbles that are actively in the stack. */ private final List<Bubble> mBubbles; + /** Bubbles that are being loaded but haven't been added to the stack just yet. */ + private final List<Bubble> mPendingBubbles; private Bubble mSelectedBubble; private boolean mExpanded; private final int mMaxBubbles; @@ -143,6 +146,7 @@ public class BubbleData { public BubbleData(Context context) { mContext = context; mBubbles = new ArrayList<>(); + mPendingBubbles = new ArrayList<>(); mStateChange = new Update(mBubbles); mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); } @@ -188,7 +192,15 @@ public class BubbleData { Bubble getOrCreateBubble(NotificationEntry entry) { Bubble bubble = getBubbleWithKey(entry.getKey()); if (bubble == null) { + // Check for it in pending + for (int i = 0; i < mPendingBubbles.size(); i++) { + Bubble b = mPendingBubbles.get(i); + if (b.getKey().equals(entry.getKey())) { + return b; + } + } bubble = new Bubble(entry); + mPendingBubbles.add(bubble); } else { bubble.setEntry(entry); } @@ -204,7 +216,7 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + bubble); } - + mPendingBubbles.remove(bubble); // No longer pending once we're here Bubble prevBubble = getBubbleWithKey(bubble.getKey()); suppressFlyout |= !shouldShowFlyout(bubble.getEntry()); @@ -377,6 +389,12 @@ public class BubbleData { } private void doRemove(String key, @DismissReason int reason) { + // If it was pending remove it + for (int i = 0; i < mPendingBubbles.size(); i++) { + if (mPendingBubbles.get(i).getKey().equals(key)) { + mPendingBubbles.remove(mPendingBubbles.get(i)); + } + } int indexToRemove = indexForKey(key); if (indexToRemove == -1) { return; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 59d68bca93c3..6528f3762473 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -97,13 +97,11 @@ public class ExpandedAnimationController private boolean mSpringingBubbleToTouch = false; private int mExpandedViewPadding; - private float mLauncherGridDiff; public ExpandedAnimationController(Point displaySize, int expandedViewPadding, int orientation) { updateOrientation(orientation, displaySize); mExpandedViewPadding = expandedViewPadding; - mLauncherGridDiff = 30f; } /** @@ -569,15 +567,7 @@ public class ExpandedAnimationController * @return Space between bubbles in row above expanded view. */ private float getSpaceBetweenBubbles() { - /** - * Ordered left to right: - * Screen edge - * [mExpandedViewPadding] - * Expanded view edge - * [launcherGridDiff] --- arbitrary value until launcher exports widths - * Launcher's app icon grid edge that we must match - */ - final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2; + final float rowMargins = mExpandedViewPadding * 2; final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins; final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index c9faf69cfd6f..4e887262659e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -91,7 +91,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi if (mDebuggable) { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_AOD_BRIGHTNESS); - mBroadcastDispatcher.registerReceiver(this, filter, handler, UserHandle.ALL); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, handler, UserHandle.ALL); } } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 59ac329e1983..48750fa5e769 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -233,7 +233,7 @@ public class PowerUI extends SystemUI implements CommandQueue.Callbacks { filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(this, filter, mHandler); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index f710f7fc47e2..f66a1ece1868 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -68,7 +68,7 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil override fun updateResources(): Boolean { with(mContext.resources) { smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size) - cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) + cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2 cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin) } requestLayout() diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index f7e4c794836e..1077834e7146 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -124,7 +124,7 @@ public class QSMediaPlayer { } } }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay)); + btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); btn.setImageTintList(ColorStateList.valueOf(mForegroundColor)); btn.setVisibility(View.VISIBLE); @@ -199,8 +199,7 @@ public class QSMediaPlayer { List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser()); if (info != null) { for (ResolveInfo inf : info) { - if (inf.activityInfo.packageName.equals(notif.contentIntent.getCreatorPackage())) { - Log.d(TAG, "Found receiver for package: " + inf); + if (inf.activityInfo.packageName.equals(mController.getPackageName())) { mRecvComponent = inf.getComponentInfo().getComponentName(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 51e352b30019..35b8312ba25c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -184,21 +184,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne // Add media carousel if (useQsMediaPlayer(context)) { - HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext); - mediaScrollView.setHorizontalScrollBarEnabled(false); - int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height); - int padding = (int) getResources().getDimension(R.dimen.qs_media_padding); - LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight); - lpView.setMarginStart(padding); - lpView.setMarginEnd(padding); - addView(mediaScrollView, lpView); - - LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - mMediaCarousel = new LinearLayout(mContext); - mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL); - mediaScrollView.addView(mMediaCarousel, lpCarousel); - mediaScrollView.setVisibility(View.GONE); + HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from( + mContext).inflate(R.layout.media_carousel, this, false); + mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel); + addView(mediaScrollView); } else { mMediaCarousel = null; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index d40e25064352..cec1cb2fb53b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -106,7 +106,7 @@ public class QuickQSMediaPlayer { } } }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay)); + btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); btn.setImageTintList(ColorStateList.valueOf(mForegroundColor)); btn.setVisibility(View.VISIBLE); } @@ -136,14 +136,25 @@ public class QuickQSMediaPlayer { * @param actionsContainer a LinearLayout containing the media action buttons * @param actionsToShow indices of which actions to display in the mini player * (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT) + * @param contentIntent Intent to send when user taps on the view */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor, - View actionsContainer, int[] actionsToShow) { - Log.d(TAG, "Setting media session: " + token); + View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) { mToken = token; mForegroundColor = iconColor; mBackgroundColor = bgColor; - mController = new MediaController(mContext, token); + + String oldPackage = ""; + if (mController != null) { + oldPackage = mController.getPackageName(); + } + MediaController controller = new MediaController(mContext, token); + boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName()); + if (mController != null && !samePlayer && !isPlaying(controller)) { + // Only update if this is a different session and currently playing + return; + } + mController = controller; MediaMetadata mMediaMetadata = mController.getMetadata(); // Try to find a receiver for the media button that matches this app @@ -153,7 +164,6 @@ public class QuickQSMediaPlayer { if (info != null) { for (ResolveInfo inf : info) { if (inf.activityInfo.packageName.equals(mController.getPackageName())) { - Log.d(TAG, "Found receiver for package: " + inf); mRecvComponent = inf.getComponentInfo().getComponentName(); } } @@ -165,6 +175,16 @@ public class QuickQSMediaPlayer { return; } + // Action + mMediaNotifView.setOnClickListener(v -> { + try { + contentIntent.send(); + mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent was canceled: " + e.getMessage()); + } + }); + // Album art addAlbumArtBackground(mMediaMetadata, mBackgroundColor); @@ -237,12 +257,12 @@ public class QuickQSMediaPlayer { * Check whether the media controlled by this player is currently playing * @return whether it is playing, or false if no controller information */ - public boolean isPlaying() { - if (mController == null) { + public boolean isPlaying(MediaController controller) { + if (controller == null) { return false; } - PlaybackState state = mController.getPlaybackState(); + PlaybackState state = controller.getPlaybackState(); if (state == null) { return false; } @@ -261,12 +281,11 @@ public class QuickQSMediaPlayer { private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) { Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); - if (albumArt != null) { - Rect bounds = new Rect(); - mMediaNotifView.getBoundsOnScreen(bounds); - int width = bounds.width(); - int height = bounds.height(); - + Rect bounds = new Rect(); + mMediaNotifView.getBoundsOnScreen(bounds); + int width = bounds.width(); + int height = bounds.height(); + if (albumArt != null && width > 0 && height > 0) { Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); Bitmap scaled = scaleBitmap(original, width, height); Canvas canvas = new Canvas(scaled); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index db52e7d37a92..b05d4fdf7db7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -85,20 +85,19 @@ public class QuickQSPanel extends QSPanel { mHorizontalLinearLayout.setClipChildren(false); mHorizontalLinearLayout.setClipToPadding(false); - LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); - mTileLayout = new DoubleLineTileLayout(context); mMediaTileLayout = mTileLayout; mRegularTileLayout = new HeaderTileLayout(context); + LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); lp.setMarginEnd(10); lp.setMarginStart(0); mHorizontalLinearLayout.addView((View) mTileLayout, lp); mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout); - - lp.setMarginEnd(0); - lp.setMarginStart(10); - mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp); + LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); + lp2.setMarginEnd(0); + lp2.setMarginStart(25); + mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2); sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index bbff117c6f81..ad79cadcc12d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -322,7 +322,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); try { mUserReceiverRegistered.set(true); - mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser); } catch (Exception ex) { mUserReceiverRegistered.set(false); Log.e(TAG, "Could not register unlock receiver", ex); diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java index 077d2602e43f..9599d77bf65a 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java +++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java @@ -98,7 +98,8 @@ public abstract class CurrentUserTracker { if (!mReceiverRegistered) { mCurrentUserId = ActivityManager.getCurrentUser(); IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL); + mBroadcastDispatcher.registerReceiver(this, filter, null, + UserHandle.ALL); mReceiverRegistered = true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 667e721ae37d..43d0399c6d62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -584,15 +584,7 @@ public class NotificationRemoteInputManager implements Dumpable { public void bindRow(ExpandableNotificationRow row) { row.setRemoteInputController(mRemoteInputController); - } - - /** - * Return on-click handler for notification remote views - * - * @return on-click handler - */ - public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() { - return mOnClickHandler; + row.setRemoteViewClickHandler(mOnClickHandler); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java index ec8dbead7de2..d1f6ebf3826d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java @@ -18,8 +18,6 @@ package com.android.systemui.statusbar; import android.content.Context; -import com.android.systemui.statusbar.notification.row.NotificationRowModule; - import javax.inject.Singleton; import dagger.Module; @@ -28,7 +26,7 @@ import dagger.Provides; /** * Dagger Module providing common dependencies of StatusBar. */ -@Module(includes = {NotificationRowModule.class}) +@Module public class StatusBarDependenciesModule { /** * Provides our instance of CommandQueue which is considered optional. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index dc84b5785ad9..9c626f7b877d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -307,6 +307,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi case Icon.TYPE_RESOURCE: return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId(); case Icon.TYPE_URI: + case Icon.TYPE_URI_ADAPTIVE_BITMAP: return a.getUriString().equals(b.getUriString()); default: return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java index ec1efa58868e..b960b42b3e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Objects; /** * Represents a set of grouped notifications. The final notification list is usually a mix of @@ -58,22 +57,15 @@ public class GroupEntry extends ListEntry { @VisibleForTesting public void setSummary(@Nullable NotificationEntry summary) { - if (!Objects.equals(mSummary, summary)) { - mSummary = summary; - onGroupingUpdated(); - } + mSummary = summary; } void clearChildren() { - if (mChildren.size() != 0) { - mChildren.clear(); - onGroupingUpdated(); - } + mChildren.clear(); } void addChild(NotificationEntry child) { mChildren.add(child); - onGroupingUpdated(); } void sortChildren(Comparator<? super NotificationEntry> c) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index 601b3e053e8e..dc68c4bdba78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -18,38 +18,20 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.Nullable; -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.notification.collection.provider.DerivedMember; -import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider; -import com.android.systemui.statusbar.phone.NotificationGroupManager; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - /** * Abstract superclass for top-level entries, i.e. things that can appear in the final notification * list shown to users. In practice, this means either GroupEntries or NotificationEntries. */ public abstract class ListEntry { private final String mKey; - private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider(); - private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider); @Nullable private GroupEntry mParent; @Nullable private GroupEntry mPreviousParent; private int mSection; int mFirstAddedIteration = -1; - // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic - // replaced in GroupEntry and NotifListBuilderImpl - private final NotificationGroupManager mGroupManager; - ListEntry(String key) { mKey = key; - - // TODO: (b/145659174) remove - mGroupManager = Dependency.get(NotificationGroupManager.class); } public String getKey() { @@ -68,11 +50,7 @@ public abstract class ListEntry { } void setParent(@Nullable GroupEntry parent) { - if (!Objects.equals(mParent, parent)) { - invalidateParent(); - mParent = parent; - onGroupingUpdated(); - } + mParent = parent; } @Nullable public GroupEntry getPreviousParent() { @@ -91,58 +69,4 @@ public abstract class ListEntry { void setSection(int section) { mSection = section; } - - /** - * Resets the cached values of DerivedMembers. - */ - void invalidateDerivedMembers() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).invalidate(); - } - } - - /** - * Whether this notification is shown to the user as a high priority notification: visible on - * the lock screen/status bar and in the top section in the shade. - */ - public boolean isHighPriority() { - return mIsHighPriorityProvider.get(this); - } - - private void invalidateParent() { - // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children - if (getParent() != null) { - getParent().invalidateDerivedMembers(); - } - - // TODO: (b/145659174) remove - final NotificationEntry notifEntry = getRepresentativeEntry(); - if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) { - NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn()); - if (summary != null) { - summary.invalidateDerivedMembers(); - } - } - } - - void onGroupingUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onGroupingUpdated(); - } - invalidateParent(); - } - - void onSbnUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onSbnUpdated(); - } - invalidateParent(); - } - - void onRankingUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onRankingUpdated(); - } - invalidateParent(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 28e486df29b9..7301fe1df398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -204,11 +204,8 @@ public final class NotificationEntry extends ListEntry { + " doesn't match existing key " + mKey); } - if (!Objects.equals(mSbn, sbn)) { - mSbn = sbn; - mBubbleMetadata = mSbn.getNotification().getBubbleMetadata(); - onSbnUpdated(); - } + mSbn = sbn; + mBubbleMetadata = mSbn.getNotification().getBubbleMetadata(); } /** @@ -233,10 +230,7 @@ public final class NotificationEntry extends ListEntry { + " doesn't match existing key " + mKey); } - if (!Objects.equals(mRanking, ranking)) { - mRanking = ranking; - onRankingUpdated(); - } + mRanking = ranking; } /* diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 7010943559ba..3bbd722517f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -24,6 +24,7 @@ import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.logging.NotifEvent import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier @@ -54,7 +55,8 @@ open class NotificationRankingManager @Inject constructor( private val notifFilter: NotificationFilter, private val notifLog: NotifLog, sectionsFeatureManager: NotificationSectionsFeatureManager, - private val peopleNotificationIdentifier: PeopleNotificationIdentifier + private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val highPriorityProvider: HighPriorityProvider ) { var rankingMap: RankingMap? = null @@ -81,6 +83,9 @@ open class NotificationRankingManager @Inject constructor( val aHeadsUp = a.isRowHeadsUp val bHeadsUp = b.isRowHeadsUp + val aIsHighPriority = a.isHighPriority() + val bIsHighPriority = b.isHighPriority() + when { usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1 aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1 @@ -90,8 +95,8 @@ open class NotificationRankingManager @Inject constructor( aMedia != bMedia -> if (aMedia) -1 else 1 // Upsort PRIORITY_MAX system notifications aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1 - a.isHighPriority != b.isHighPriority -> - -1 * a.isHighPriority.compareTo(b.isHighPriority) + aIsHighPriority != bIsHighPriority -> + -1 * aIsHighPriority.compareTo(bIsHighPriority) aRank != bRank -> aRank - bRank else -> nb.notification.`when`.compareTo(na.notification.`when`) } @@ -154,7 +159,7 @@ open class NotificationRankingManager @Inject constructor( ) { if (usePeopleFiltering && entry.isPeopleNotification()) { entry.bucket = BUCKET_PEOPLE - } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) { + } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) { entry.bucket = BUCKET_ALERTING } else { entry.bucket = BUCKET_SILENT @@ -178,10 +183,6 @@ open class NotificationRankingManager @Inject constructor( // TODO: notify group manager here? groupManager.onEntryUpdated(entry, oldSbn) } - - // TODO: (b/145659174) remove after moving to new NotifPipeline - // (should be able to remove all groupManager code post-migration) - entry.invalidateDerivedMembers() } } } @@ -191,6 +192,9 @@ open class NotificationRankingManager @Inject constructor( sbn.isPeopleNotification() private fun StatusBarNotification.isPeopleNotification() = peopleNotificationIdentifier.isPeopleNotification(this) + + private fun NotificationEntry.isHighPriority() = + highPriorityProvider.isHighPriority(this) } // Convenience functions diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java index 20f206b91f10..6dc647d33046 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java @@ -41,8 +41,8 @@ import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -65,7 +65,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Dependency.get(NotificationInterruptionStateProvider.class); private final Context mContext; - private final NotificationRowContentBinder mRowContentBinder; private final NotificationMessagingUtil mMessagingUtil; private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = this::logNotificationExpansion; @@ -77,7 +76,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; private HeadsUpManager mHeadsUpManager; - private NotificationRowContentBinder.InflationCallback mInflationCallback; + private NotificationContentInflater.InflationCallback mInflationCallback; private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; @@ -85,13 +84,11 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { public NotificationRowBinderImpl( Context context, - NotificationRowContentBinder rowContentBinder, boolean allowLongPress, KeyguardBypassController keyguardBypassController, StatusBarStateController statusBarStateController, NotificationLogger logger) { mContext = context; - mRowContentBinder = rowContentBinder; mMessagingUtil = new NotificationMessagingUtil(context); mAllowLongPress = allowLongPress; mKeyguardBypassController = keyguardBypassController; @@ -120,7 +117,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mOnAppOpsClickListener = mGutsManager::openGuts; } - public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { + public void setInflationCallback(NotificationContentInflater.InflationCallback callback) { mInflationCallback = callback; } @@ -159,6 +156,19 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private void bindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row, Runnable onDismissRunnable) { + row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey()); + row.setBypassController(mKeyguardBypassController); + row.setStatusBarStateController(mStatusBarStateController); + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setOnExpandClickListener(mPresenter); + row.setInflationCallback(mInflationCallback); + if (mAllowLongPress) { + row.setLongPressListener(mGutsManager::openGuts); + } + mListContainer.bindRow(row); + getRemoteInputManager().bindRow(row); + // Get the app name. // Note that Notification.Builder#bindHeaderAppName has similar logic // but since this field is used in the guts, it must be accurate. @@ -176,33 +186,15 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { } catch (PackageManager.NameNotFoundException e) { // Do nothing } - - row.initialize( - appname, - sbn.getKey(), - mExpansionLogger, - mKeyguardBypassController, - mGroupManager, - mHeadsUpManager, - mRowContentBinder, - mPresenter); - - // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely - row.setStatusBarStateController(mStatusBarStateController); - row.setInflationCallback(mInflationCallback); - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } - mListContainer.bindRow(row); - getRemoteInputManager().bindRow(row); - + row.setAppName(appname); row.setOnDismissRunnable(onDismissRunnable); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (ENABLE_REMOTE_INPUT) { row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); } + row.setAppOpsOnClickListener(mOnAppOpsClickListener); + mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 9312c2260d46..db107f531e9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; @@ -62,6 +63,7 @@ public class KeyguardCoordinator implements Coordinator { private final BroadcastDispatcher mBroadcastDispatcher; private final StatusBarStateController mStatusBarStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final HighPriorityProvider mHighPriorityProvider; @Inject public KeyguardCoordinator( @@ -71,15 +73,16 @@ public class KeyguardCoordinator implements Coordinator { NotificationLockscreenUserManager lockscreenUserManager, BroadcastDispatcher broadcastDispatcher, StatusBarStateController statusBarStateController, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + HighPriorityProvider highPriorityProvider) { mContext = context; mMainHandler = mainThreadHandler; mKeyguardStateController = keyguardStateController; mLockscreenUserManager = lockscreenUserManager; - mBroadcastDispatcher = broadcastDispatcher; mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mHighPriorityProvider = highPriorityProvider; } @Override @@ -151,7 +154,7 @@ public class KeyguardCoordinator implements Coordinator { } if (NotificationUtils.useNewInterruptionModel(mContext) && hideSilentNotificationsOnLockscreen()) { - return entry.isHighPriority(); + return mHighPriorityProvider.isHighPriority(entry); } else { return entry.getRepresentativeEntry() != null && !entry.getRepresentativeEntry().getRanking().isAmbient(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index eeb54abf92b4..2436bb9f82f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -76,6 +76,7 @@ public class NotifCoordinators implements Dumpable { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(); pw.println(TAG + ":"); for (Coordinator c : mCoordinators) { pw.println("\t" + c.getClass()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java index 069c15f9d7a2..c3e3c5373b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java @@ -259,7 +259,7 @@ public class GroupCoalescer implements Dumpable { pw.println("Coalesced notifications:"); for (EventBatch batch : mBatches.values()) { pw.println(" Batch " + batch.mGroupKey + ":"); - pw.println(" Created" + (now - batch.mCreatedTimestamp) + "ms ago"); + pw.println(" Created " + (now - batch.mCreatedTimestamp) + "ms ago"); for (CoalescedEvent event : batch.mMembers) { pw.println(" " + event.getKey()); eventCount++; @@ -299,5 +299,5 @@ public class GroupCoalescer implements Dumpable { void onNotificationBatchPosted(List<CoalescedEvent> events); } - private static final int GROUP_LINGER_DURATION = 40; + private static final int GROUP_LINGER_DURATION = 500; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java deleted file mode 100644 index 815e6f7eaa46..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 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.systemui.statusbar.notification.collection.provider; -/** - * Caches a computed value until invalidate() is called - * @param <Parent> Object used to computeValue - * @param <Value> type of value to cache until invalidate is called - */ -public abstract class DerivedMember<Parent, Value> { - private Value mValue; - protected abstract Value computeValue(Parent parent); - - /** - * Gets the last cached value, else recomputes the value. - */ - public Value get(Parent parent) { - if (mValue == null) { - mValue = computeValue(parent); - } - return mValue; - } - - /** - * Resets the cached value. - * Next time "get" is called, the value is recomputed. - */ - public void invalidate() { - mValue = null; - } - - /** - * Called when a NotificationEntry's status bar notification has updated. - * Derived members can invalidate here. - */ - public void onSbnUpdated() {} - - /** - * Called when a NotificationEntry's Ranking has updated. - * Derived members can invalidate here. - */ - public void onRankingUpdated() {} - - /** - * Called when a ListEntry's grouping information (parent or children) has changed. - * Derived members can invalidate here. - */ - public void onGroupingUpdated() {} -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java new file mode 100644 index 000000000000..3cc5e62c0bda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -0,0 +1,109 @@ +/* + * 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.collection.provider; + +import android.app.Notification; +import android.app.NotificationManager; + +import com.android.systemui.statusbar.notification.collection.GroupEntry; +import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Determines whether a notification is considered 'high priority'. + * + * Notifications that are high priority are visible on the lock screen/status bar and in the top + * section in the shade. + */ +@Singleton +public class HighPriorityProvider { + private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; + + @Inject + public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) { + mPeopleNotificationIdentifier = peopleNotificationIdentifier; + } + + /** + * @return true if the ListEntry is high priority, else false + * + * A NotificationEntry is considered high priority if it: + * - has importance greater than or equal to IMPORTANCE_DEFAULT + * OR + * - their importance has NOT been set to a low priority option by the user AND the + * notification fulfills one of the following: + * - has a person associated with it + * - has a media session associated with it + * - has messaging style + * + * A GroupEntry is considered high priority if its representativeEntry (summary) or children are + * high priority + */ + public boolean isHighPriority(ListEntry entry) { + if (entry == null) { + return false; + } + + final NotificationEntry notifEntry = entry.getRepresentativeEntry(); + return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT + || hasHighPriorityCharacteristics(notifEntry) + || hasHighPriorityChild(entry); + } + + + private boolean hasHighPriorityChild(ListEntry entry) { + if (entry instanceof GroupEntry) { + for (NotificationEntry child : ((GroupEntry) entry).getChildren()) { + if (isHighPriority(child)) { + return true; + } + } + } + return false; + } + + private boolean hasHighPriorityCharacteristics(NotificationEntry entry) { + return !hasUserSetImportance(entry) + && (isImportantOngoing(entry) + || entry.getSbn().getNotification().hasMediaSession() + || isPeopleNotification(entry) + || isMessagingStyle(entry)); + } + + private boolean isImportantOngoing(NotificationEntry entry) { + return entry.getSbn().getNotification().isForegroundService() + && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW; + } + + private boolean isMessagingStyle(NotificationEntry entry) { + return Notification.MessagingStyle.class.equals( + entry.getSbn().getNotification().getNotificationStyle()); + } + + private boolean isPeopleNotification(NotificationEntry entry) { + return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn()); + } + + private boolean hasUserSetImportance(NotificationEntry entry) { + return entry.getRanking().getChannel() != null + && entry.getRanking().getChannel().hasUserSetImportance(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java deleted file mode 100644 index 76e256b9be2d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 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.systemui.statusbar.notification.collection.provider; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Person; - -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.phone.NotificationGroupManager; - -import java.util.ArrayList; -import java.util.List; - -/** - * Whether the ListEntry is shown to the user as a high priority notification: visible on - * the lock screen/status bar and in the top section in the shade. - * - * A NotificationEntry is considered high priority if it: - * - has importance greater than or equal to IMPORTANCE_DEFAULT - * OR - * - their importance has NOT been set to a low priority option by the user AND the notification - * fulfills one of the following: - * - has a person associated with it - * - has a media session associated with it - * - has messaging style - * - * A GroupEntry is considered high priority if its representativeEntry (summary) or children are - * high priority - */ -public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> { - // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic - // replaced in GroupEntry and NotifListBuilderImpl - private final NotificationGroupManager mGroupManager; - - - public IsHighPriorityProvider() { - // TODO: (b/145659174) remove - mGroupManager = Dependency.get(NotificationGroupManager.class); - } - - @Override - protected Boolean computeValue(ListEntry entry) { - if (entry == null) { - return false; - } - - return isHighPriority(entry); - } - - private boolean isHighPriority(ListEntry listEntry) { - // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING) - final NotificationEntry notifEntry = listEntry.getRepresentativeEntry(); - return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT - || hasHighPriorityCharacteristics(notifEntry) - || hasHighPriorityChild(listEntry); - - } - - private boolean hasHighPriorityChild(ListEntry entry) { - // TODO: (b/145659174) remove - if (entry instanceof NotificationEntry) { - NotificationEntry notifEntry = (NotificationEntry) entry; - if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) { - List<NotificationEntry> logicalChildren = - mGroupManager.getLogicalChildren(notifEntry.getSbn()); - for (NotificationEntry child : logicalChildren) { - if (child.isHighPriority()) { - return true; - } - } - } - } - - if (entry instanceof GroupEntry) { - for (NotificationEntry child : ((GroupEntry) entry).getChildren()) { - if (child.isHighPriority()) { - return true; - } - } - } - return false; - } - - private boolean hasHighPriorityCharacteristics(NotificationEntry entry) { - return !hasUserSetImportance(entry) - && (isImportantOngoing(entry) - || entry.getSbn().getNotification().hasMediaSession() - || hasPerson(entry) - || isMessagingStyle(entry)); - } - - private boolean isImportantOngoing(NotificationEntry entry) { - return entry.getSbn().getNotification().isForegroundService() - && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW; - } - - private boolean isMessagingStyle(NotificationEntry entry) { - return Notification.MessagingStyle.class.equals( - entry.getSbn().getNotification().getNotificationStyle()); - } - - private boolean hasPerson(NotificationEntry entry) { - // TODO: cache favorite and recent contacts to check contact affinity - Notification notification = entry.getSbn().getNotification(); - ArrayList<Person> people = notification.extras != null - ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST) - : new ArrayList<>(); - return people != null && !people.isEmpty(); - } - - private boolean hasUserSetImportance(NotificationEntry entry) { - return entry.getRanking().getChannel() != null - && entry.getRanking().getChannel().hasUserSetImportance(); - } - - @Override - public void onSbnUpdated() { - invalidate(); - } - - @Override - public void onRankingUpdated() { - invalidate(); - } - - @Override - public void onGroupingUpdated() { - invalidate(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index a8a35d07b3f0..3c247df692f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -65,6 +65,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.Chronometer; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -149,7 +150,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private StatusBarStateController mStatusbarStateController; private KeyguardBypassController mBypassController; private LayoutListener mLayoutListener; - private NotificationRowContentBinder mNotificationContentBinder; + private final NotificationContentInflater mNotificationInflater; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; private int mMaxHeadsUpHeightBeforeN; @@ -463,7 +464,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * Inflate views based off the inflation flags set. Inflation happens asynchronously. */ public void inflateViews() { - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, false /* forceInflate */, mInflationCallback); } @@ -477,7 +478,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // View should not be reinflated in the future clearInflationFlags(inflationFlag); Runnable freeViewRunnable = - () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag); + () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag); switch (inflationFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, @@ -741,10 +742,23 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsHeadsUp || mHeadsupDisappearRunning; } + + public void setGroupManager(NotificationGroupManager groupManager) { + mGroupManager = groupManager; + mPrivateLayout.setGroupManager(groupManager); + } + public void setRemoteInputController(RemoteInputController r) { mPrivateLayout.setRemoteInputController(r); } + public void setAppName(String appName) { + mAppName = appName; + if (mMenuRow != null && mMenuRow.getMenuView() != null) { + mMenuRow.setAppName(mAppName); + } + } + public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } @@ -838,7 +852,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mIsChildInGroup = isChildInGroup; if (mIsLowPriority) { int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED; - mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams, + mNotificationInflater.bindContent(mEntry, this, flags, mBindParams, false /* forceInflate */, mInflationCallback); } } @@ -1091,6 +1105,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mPrivateLayout.getContractedNotificationHeader(); } + public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { + mOnExpandClickListener = onExpandClickListener; + } + public void setLongPressListener(LongPressListener longPressListener) { mLongPressListener = longPressListener; } @@ -1113,6 +1131,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + public HeadsUpManager getHeadsUpManager() { return mHeadsUpManager; } @@ -1237,7 +1259,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView l.reInflateViews(); } mEntry.getSbn().clearPackageContext(); - mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams, + mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams, true /* forceInflate */, mInflationCallback); } @@ -1612,6 +1634,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mBindParams.usesIncreasedHeadsUpHeight = use; } + public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { + mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler); + } + /** * Set callback for notification content inflation * @@ -1626,7 +1652,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNeedsRedaction = needsRedaction; if (needsRedaction) { setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); - mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, + mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC, mBindParams, false /* forceInflate */, mInflationCallback); } else { clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC); @@ -1635,12 +1661,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + @VisibleForTesting + public NotificationContentInflater getNotificationInflater() { + return mNotificationInflater; + } + public interface ExpansionLogger { void logNotificationExpansion(String key, boolean userAction, boolean expanded); } public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); + mNotificationInflater = new NotificationContentInflater(); mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); @@ -1648,30 +1680,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView initDimens(); } - /** - * Initialize row. - */ - public void initialize( - String appName, - String notificationKey, - ExpansionLogger logger, - KeyguardBypassController bypassController, - NotificationGroupManager groupManager, - HeadsUpManager headsUpManager, - NotificationRowContentBinder rowContentBinder, - OnExpandClickListener onExpandClickListener) { - mAppName = appName; - if (mMenuRow != null && mMenuRow.getMenuView() != null) { - mMenuRow.setAppName(mAppName); - } - mLogger = logger; - mLoggingKey = notificationKey; + public void setBypassController(KeyguardBypassController bypassController) { mBypassController = bypassController; - mGroupManager = groupManager; - mPrivateLayout.setGroupManager(groupManager); - mHeadsUpManager = headsUpManager; - mNotificationContentBinder = rowContentBinder; - mOnExpandClickListener = onExpandClickListener; } public void setStatusBarStateController(StatusBarStateController statusBarStateController) { @@ -2910,6 +2920,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return 0; } + public void setExpansionLogger(ExpansionLogger logger, String key) { + mLogger = logger; + mLoggingKey = key; + } + public void onExpandedByGesture(boolean userExpanded) { int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java deleted file mode 100644 index c11c60fcdd04..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.row; - -import android.widget.RemoteViews; - -import androidx.annotation.Nullable; - -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; - -/** - * Caches {@link RemoteViews} for a notification's content views. - */ -public interface NotifRemoteViewCache { - - /** - * Whether the notification has the remote view cached - * - * @param entry notification - * @param flag inflation flag for content view - * @return true if the remote view is cached - */ - boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag); - - /** - * Get the remote view for the content flag specified. - * - * @param entry notification - * @param flag inflation flag for the content view - * @return the remote view if it is cached, null otherwise - */ - @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag); - - /** - * Cache a remote view for a given content flag on a notification. - * - * @param entry notification - * @param flag inflation flag for the content view - * @param remoteView remote view to store - */ - void putCachedView( - NotificationEntry entry, - @InflationFlag int flag, - RemoteViews remoteView); - - /** - * Remove a cached remote view for a given content flag on a notification. - * - * @param entry notification - * @param flag inflation flag for the content view - */ - void removeCachedView(NotificationEntry entry, @InflationFlag int flag); - - /** - * Clear a notification's remote view cache. - * - * @param entry notification - */ - void clearCache(NotificationEntry entry); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java deleted file mode 100644 index a19099a6ea52..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.row; - -import android.util.ArrayMap; -import android.util.SparseArray; -import android.widget.RemoteViews; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.statusbar.NotificationVisibility; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; - -import java.util.Map; - -import javax.inject.Inject; - -/** - * Implementation of remote view cache that keeps remote views cached for all active notifications. - */ -public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache { - private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews = - new ArrayMap<>(); - - @Inject - NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) { - entryManager.addNotificationEntryListener(mEntryListener); - } - - @Override - public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) { - return getCachedView(entry, flag) != null; - } - - @Override - public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) { - return getContentViews(entry).get(flag); - } - - @Override - public void putCachedView( - NotificationEntry entry, - @InflationFlag int flag, - RemoteViews remoteView) { - getContentViews(entry).put(flag, remoteView); - } - - @Override - public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) { - getContentViews(entry).remove(flag); - } - - @Override - public void clearCache(NotificationEntry entry) { - getContentViews(entry).clear(); - } - - private @NonNull SparseArray<RemoteViews> getContentViews(NotificationEntry entry) { - SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry); - if (contentViews == null) { - throw new IllegalStateException( - String.format("Remote view cache was never created for notification %s", - entry.getKey())); - } - return contentViews; - } - - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { - @Override - public void onPendingEntryAdded(NotificationEntry entry) { - mNotifCachedContentViews.put(entry, new SparseArray<>()); - } - - @Override - public void onEntryRemoved( - NotificationEntry entry, - @Nullable NotificationVisibility visibility, - boolean removedByUser) { - mNotifCachedContentViews.remove(entry); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index e1a6747b5398..30f22ac5e161 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; @@ -27,6 +26,7 @@ import android.content.Context; import android.os.AsyncTask; import android.os.CancellationSignal; import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; import android.util.Log; import android.view.View; import android.widget.RemoteViews; @@ -35,7 +35,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.Dependency; import com.android.systemui.statusbar.InflationTask; -import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; @@ -50,30 +49,17 @@ import com.android.systemui.util.Assert; import java.util.HashMap; -import javax.inject.Inject; -import javax.inject.Singleton; - /** * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by * asynchronously building the content's {@link RemoteViews} and applying it to the row. */ -@Singleton -@VisibleForTesting(visibility = PACKAGE) public class NotificationContentInflater implements NotificationRowContentBinder { public static final String TAG = "NotifContentInflater"; + private RemoteViews.OnClickHandler mRemoteViewClickHandler; private boolean mInflateSynchronously = false; - private final NotificationRemoteInputManager mRemoteInputManager; - private final NotifRemoteViewCache mRemoteViewCache; - - @Inject - public NotificationContentInflater( - NotifRemoteViewCache remoteViewCache, - NotificationRemoteInputManager remoteInputManager) { - mRemoteViewCache = remoteViewCache; - mRemoteInputManager = remoteInputManager; - } + private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>(); @Override public void bindContent( @@ -90,27 +76,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder return; } - StatusBarNotification sbn = entry.getSbn(); + StatusBarNotification sbn = row.getEntry().getSbn(); // To check if the notification has inline image and preload inline image if necessary. row.getImageResolver().preloadImages(sbn.getNotification()); if (forceInflate) { - mRemoteViewCache.clearCache(entry); + mCachedContentViews.clear(); } AsyncInflationTask task = new AsyncInflationTask( + sbn, mInflateSynchronously, contentToBind, - mRemoteViewCache, - entry, + mCachedContentViews, row, bindParams.isLowPriority, bindParams.isChildInGroup, bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, callback, - mRemoteInputManager.getRemoteViewsOnClickHandler()); + mRemoteViewClickHandler); if (mInflateSynchronously) { task.onPostExecute(task.doInBackground()); } else { @@ -137,15 +123,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, row.getHeadsUpManager(), row.getExistingSmartRepliesAndActions()); - apply( inflateSynchronously, result, reInflateFlags, - mRemoteViewCache, - entry, + mCachedContentViews, row, - mRemoteInputManager.getRemoteViewsOnClickHandler(), + mRemoteViewClickHandler, null); return result; } @@ -165,7 +149,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder int curFlag = 1; while (contentToUnbind != 0) { if ((contentToUnbind & curFlag) != 0) { - freeNotificationView(entry, row, curFlag); + freeNotificationView(row, curFlag); } contentToUnbind &= ~curFlag; curFlag = curFlag << 1; @@ -173,25 +157,34 @@ public class NotificationContentInflater implements NotificationRowContentBinder } /** + * Set click handler for notification remote views + * + * @param remoteViewClickHandler click handler for remote views + */ + public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { + mRemoteViewClickHandler = remoteViewClickHandler; + } + + /** * Frees the content view associated with the inflation flag. Will only succeed if the * view is safe to remove. * * @param inflateFlag the flag corresponding to the content view which should be freed */ - private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row, + private void freeNotificationView(ExpandableNotificationRow row, @InflationFlag int inflateFlag) { switch (inflateFlag) { case FLAG_CONTENT_VIEW_HEADS_UP: if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) { row.getPrivateLayout().setHeadsUpChild(null); - mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); + mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP); row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null); } break; case FLAG_CONTENT_VIEW_PUBLIC: if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) { row.getPublicLayout().setContractedChild(null); - mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC); + mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC); } break; case FLAG_CONTENT_VIEW_CONTRACTED: @@ -252,12 +245,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result; } - private static CancellationSignal apply( + public static CancellationSignal apply( boolean inflateSynchronously, InflationProgress result, @InflationFlag int reInflateFlags, - NotifRemoteViewCache remoteViewCache, - NotificationEntry entry, + ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row, RemoteViews.OnClickHandler remoteViewClickHandler, @Nullable InflationCallback callback) { @@ -269,7 +261,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newContentView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)); + cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -281,8 +273,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newContentView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, callback, privateLayout, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, + row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); @@ -293,7 +285,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newExpandedView != null) { boolean isNewView = !canReapplyRemoteView(result.newExpandedView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)); + cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -305,8 +297,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newExpandedView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, + cachedContentViews, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getExpandedChild(), privateLayout.getVisibleWrapper( NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, @@ -319,7 +311,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.newHeadsUpView != null) { boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)); + cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -331,8 +323,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newHeadsUpView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, + cachedContentViews, row, isNewView, remoteViewClickHandler, callback, privateLayout, privateLayout.getHeadsUpChild(), privateLayout.getVisibleWrapper( VISIBLE_TYPE_HEADSUP), runningInflations, @@ -344,7 +336,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & flag) != 0) { boolean isNewView = !canReapplyRemoteView(result.newPublicView, - remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)); + cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC)); ApplyCallback applyCallback = new ApplyCallback() { @Override public void setResultView(View v) { @@ -356,16 +348,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder return result.newPublicView; } }; - applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache, - entry, row, isNewView, remoteViewClickHandler, callback, + applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews, + row, isNewView, remoteViewClickHandler, callback, publicLayout, publicLayout.getContractedChild(), publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), runningInflations, applyCallback); } // Let's try to finish, maybe nobody is even inflating anything - finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry, - row); + finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener( () -> runningInflations.values().forEach(CancellationSignal::cancel)); @@ -378,8 +369,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder final InflationProgress result, final @InflationFlag int reInflateFlags, @InflationFlag int inflationId, - final NotifRemoteViewCache remoteViewCache, - final NotificationEntry entry, + final ArrayMap<Integer, RemoteViews> cachedContentViews, final ExpandableNotificationRow row, boolean isNewView, RemoteViews.OnClickHandler remoteViewClickHandler, @@ -432,8 +422,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder existingWrapper.onReinflated(); } runningInflations.remove(inflationId); - finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, - callback, entry, row); + finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, + callback, row); } @Override @@ -498,11 +488,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder * @return true if the inflation was finished */ private static boolean finishIfDone(InflationProgress result, - @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, + @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews, HashMap<Integer, CancellationSignal> runningInflations, - @Nullable InflationCallback endListener, NotificationEntry entry, - ExpandableNotificationRow row) { + @Nullable InflationCallback endListener, ExpandableNotificationRow row) { Assert.isMainThread(); + NotificationEntry entry = row.getEntry(); NotificationContentView privateLayout = row.getPrivateLayout(); NotificationContentView publicLayout = row.getPublicLayout(); if (runningInflations.isEmpty()) { @@ -510,27 +500,23 @@ public class NotificationContentInflater implements NotificationRowContentBinder if (result.inflatedContentView != null) { // New view case privateLayout.setContractedChild(result.inflatedContentView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, - result.newContentView); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { + cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) { // Reinflation case. Only update if it's still cached (i.e. view has not been // freed while inflating). - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, - result.newContentView); + cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView); } } if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { if (result.inflatedExpandedView != null) { privateLayout.setExpandedChild(result.inflatedExpandedView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, - result.newExpandedView); + cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); } else if (result.newExpandedView == null) { privateLayout.setExpandedChild(null); - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, - result.newExpandedView); + cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) { + cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView); } if (result.newExpandedView != null) { privateLayout.setExpandedInflatedSmartReplies( @@ -544,14 +530,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { if (result.inflatedHeadsUpView != null) { privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, - result.newHeadsUpView); + cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); } else if (result.newHeadsUpView == null) { privateLayout.setHeadsUpChild(null); - remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, - result.newHeadsUpView); + cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) { + cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView); } if (result.newHeadsUpView != null) { privateLayout.setHeadsUpInflatedSmartReplies( @@ -564,18 +548,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { if (result.inflatedPublicView != null) { publicLayout.setContractedChild(result.inflatedPublicView); - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, - result.newPublicView); - } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { - remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, - result.newPublicView); + cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); + } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) { + cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView); } } entry.headsUpStatusBarText = result.headsUpStatusBarText; entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { - endListener.onAsyncInflationFinished(entry, reInflateFlags); + endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags); } return true; } @@ -633,7 +615,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> implements InflationCallback, InflationTask { - private final NotificationEntry mEntry; + private final StatusBarNotification mSbn; private final Context mContext; private final boolean mInflateSynchronously; private final boolean mIsLowPriority; @@ -642,17 +624,17 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final InflationCallback mCallback; private final boolean mUsesIncreasedHeadsUpHeight; private @InflationFlag int mReInflateFlags; - private final NotifRemoteViewCache mRemoteViewCache; + private final ArrayMap<Integer, RemoteViews> mCachedContentViews; private ExpandableNotificationRow mRow; private Exception mError; private RemoteViews.OnClickHandler mRemoteViewClickHandler; private CancellationSignal mCancellationSignal; private AsyncInflationTask( + StatusBarNotification notification, boolean inflateSynchronously, @InflationFlag int reInflateFlags, - NotifRemoteViewCache cache, - NotificationEntry entry, + ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row, boolean isLowPriority, boolean isChildInGroup, @@ -660,11 +642,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder boolean usesIncreasedHeadsUpHeight, InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) { - mEntry = entry; mRow = row; + mSbn = notification; mInflateSynchronously = inflateSynchronously; mReInflateFlags = reInflateFlags; - mRemoteViewCache = cache; + mCachedContentViews = cachedContentViews; mContext = mRow.getContext(); mIsLowPriority = isLowPriority; mIsChildInGroup = isChildInGroup; @@ -672,6 +654,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; mRemoteViewClickHandler = remoteViewClickHandler; mCallback = callback; + NotificationEntry entry = row.getEntry(); entry.setInflationTask(this); } @@ -684,13 +667,12 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override protected InflationProgress doInBackground(Void... params) { try { - final StatusBarNotification sbn = mEntry.getSbn(); final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(mContext, - sbn.getNotification()); + mSbn.getNotification()); - Context packageContext = sbn.getPackageContext(mContext); - Notification notification = sbn.getNotification(); + Context packageContext = mSbn.getPackageContext(mContext); + Notification notification = mSbn.getNotification(); if (notification.isMediaNotification()) { MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, packageContext); @@ -699,7 +681,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, packageContext); - return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry, + return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(), mRow.getContext(), packageContext, mRow.getHeadsUpManager(), mRow.getExistingSmartRepliesAndActions()); } catch (Exception e) { @@ -712,15 +694,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder protected void onPostExecute(InflationProgress result) { if (mError == null) { mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags, - mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this); + mCachedContentViews, mRow, mRemoteViewClickHandler, this); } else { handleError(mError); } } private void handleError(Exception e) { - mEntry.onInflationTaskFinished(); - StatusBarNotification sbn = mEntry.getSbn(); + mRow.getEntry().onInflationTaskFinished(); + StatusBarNotification sbn = mRow.getEntry().getSbn(); final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); @@ -754,10 +736,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags) { - mEntry.onInflationTaskFinished(); + mRow.getEntry().onInflationTaskFinished(); mRow.onNotificationUpdated(); if (mCallback != null) { - mCallback.onAsyncInflationFinished(mEntry, inflatedFlags); + mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags); } // Notify the resolver that the inflation task has finished, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6f2abba128d6..779a224ecb62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.StatusBar; @@ -81,6 +82,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final Context mContext; private final VisualStabilityManager mVisualStabilityManager; private final AccessibilityManager mAccessibilityManager; + private final HighPriorityProvider mHighPriorityProvider; // Dependencies: private final NotificationLockscreenUserManager mLockscreenUserManager = @@ -109,12 +111,14 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @Inject public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager, Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler, - AccessibilityManager accessibilityManager) { + AccessibilityManager accessibilityManager, + HighPriorityProvider highPriorityProvider) { mContext = context; mVisualStabilityManager = visualStabilityManager; mStatusBarLazy = statusBarLazy; mMainHandler = mainHandler; mAccessibilityManager = accessibilityManager; + mHighPriorityProvider = highPriorityProvider; } public void setUpWithPresenter(NotificationPresenter presenter, @@ -331,8 +335,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx row.getIsNonblockable(), isForBlockingHelper, row.getEntry().getImportance(), - row.getEntry().isHighPriority()); - + mHighPriorityProvider.isHighPriority(row.getEntry())); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index b4ccb567504a..edfd1b4d3c85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -46,6 +46,7 @@ import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.AlphaOptimizedImageView; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -269,7 +270,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } mAppOpsItem = createAppOpsItem(mContext); if (mIsUsingBidirectionalSwipe) { - mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority()); + mInfoItem = createInfoItem(mContext, + mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT); } else { mInfoItem = createInfoItem(mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java deleted file mode 100644 index df8653cf2406..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.row; - -import javax.inject.Singleton; - -import dagger.Binds; -import dagger.Module; - -/** - * Dagger Module containing notification row and view inflation implementations. - */ -@Module -public abstract class NotificationRowModule { - /** - * Provides notification row content binder instance. - */ - @Binds - @Singleton - public abstract NotificationRowContentBinder provideNotificationRowContentBinder( - NotificationContentInflater contentBinderImpl); - - /** - * Provides notification remote view cache instance. - */ - @Binds - @Singleton - public abstract NotifRemoteViewCache provideNotifRemoteViewCache( - NotifRemoteViewCacheImpl cacheImpl); -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index 352ba0f75a32..76fdfc6fbabc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -28,6 +28,7 @@ import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.metrics.LogMaker; import android.os.Handler; +import android.service.notification.StatusBarNotification; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -176,27 +177,30 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras .getParcelable(Notification.EXTRA_MEDIA_SESSION); - if (Utils.useQsMediaPlayer(mContext)) { + if (Utils.useQsMediaPlayer(mContext) && token != null) { final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras .getIntArray(Notification.EXTRA_COMPACT_ACTIONS); int tintColor = getNotificationHeader().getOriginalIconColor(); StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class); QuickQSPanel panel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_qs_panel); + StatusBarNotification sbn = mRow.getEntry().getSbn(); + Notification notif = sbn.getNotification(); panel.getMediaPlayer().setMediaSession(token, - mRow.getEntry().getSbn().getNotification().getSmallIcon(), + notif.getSmallIcon(), tintColor, mBackgroundColor, mActions, - compactActions); + compactActions, + notif.contentIntent); QSPanel bigPanel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_settings_panel); bigPanel.addMediaSession(token, - mRow.getEntry().getSbn().getNotification().getSmallIcon(), + notif.getSmallIcon(), tintColor, mBackgroundColor, mActions, - mRow.getEntry().getSbn()); + sbn); } boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar(); 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 a3b1b5f8360b..a6842badc153 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -366,8 +366,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(), - UserHandle.ALL); + mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, + Handler.getMain(), UserHandle.ALL); notifyNavigationBarScreenOn(); mOverviewProxyService.addCallback(mOverviewProxyListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 896b6e570da2..fe0739f9088c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; @@ -427,7 +428,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis * The notification is still pending inflation but we've decided that we no longer need * the content view (e.g. suppression might have changed and we decided we need to transfer * back). However, there is no way to abort just this inflation if other inflation requests - * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead + * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead * we just flag it as aborted and free when it's inflated. */ boolean mAbortOnInflation; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 5b34aa7781c1..00e38f814dff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -179,7 +179,7 @@ public class PhoneStatusBarPolicy filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); - broadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler); + broadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler); // listen for user / profile change. try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 30825ed65eb3..ccc86b1f8c5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -205,7 +205,6 @@ import com.android.systemui.statusbar.notification.collection.init.NewNotifPipel import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.BatteryController; @@ -414,7 +413,6 @@ public class StatusBar extends SystemUI implements DemoMode, private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; private final NotificationEntryManager mEntryManager; - private final NotificationRowContentBinder mRowContentBinder; private NotificationListController mNotificationListController; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final NotificationViewHierarchyManager mViewHierarchyManager; @@ -636,7 +634,6 @@ public class StatusBar extends SystemUI implements DemoMode, NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -718,7 +715,6 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager = notificationGutsManager; mNotificationLogger = notificationLogger; mEntryManager = notificationEntryManager; - mRowContentBinder = notificationRowContentBinder; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mViewHierarchyManager = notificationViewHierarchyManager; mKeyguardViewMediator = keyguardViewMediator; @@ -1244,7 +1240,6 @@ public class StatusBar extends SystemUI implements DemoMode, final NotificationRowBinderImpl rowBinder = new NotificationRowBinderImpl( mContext, - mRowContentBinder, mAllowNotificationLongPress, mKeyguardBypassController, mStatusBarStateController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java index df741079de29..153ca22933a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java @@ -69,7 +69,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -126,7 +125,6 @@ public class StatusBarModule { NotificationGutsManager notificationGutsManager, NotificationLogger notificationLogger, NotificationEntryManager notificationEntryManager, - NotificationRowContentBinder notificationRowContentBinder, NotificationInterruptionStateProvider notificationInterruptionStateProvider, NotificationViewHierarchyManager notificationViewHierarchyManager, KeyguardViewMediator keyguardViewMediator, @@ -209,7 +207,6 @@ public class StatusBarModule { notificationGutsManager, notificationLogger, notificationEntryManager, - notificationRowContentBinder, notificationInterruptionStateProvider, notificationViewHierarchyManager, keyguardViewMediator, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index ddacc3aedce0..4f0af9e166c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -188,7 +188,7 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C // NOTE: This receiver could run before this method returns, as it's not dispatching // on the main thread and BroadcastDispatcher may not need to register with Context. // The receiver will return immediately if the view does not have a Handler yet. - mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, + mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, Dependency.get(Dependency.TIME_TICK_HANDLER), UserHandle.ALL); Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS, StatusBarIconController.ICON_BLACKLIST); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java index 2e26711a3578..b4c154aa28cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java @@ -96,7 +96,7 @@ public class DateView extends TextView { filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(Intent.ACTION_LOCALE_CHANGED); - mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, + mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, Dependency.get(Dependency.TIME_TICK_HANDLER)); updateClock(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 570f153a62c0..cb40d7752f53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -79,7 +79,8 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio IntentFilter filter = new IntentFilter(); filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION); filter.addAction(LocationManager.MODE_CHANGED_ACTION); - mBroadcastDispatcher.registerReceiver(this, filter, new Handler(bgLooper), UserHandle.ALL); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, + new Handler(bgLooper), UserHandle.ALL); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mStatusBarManager diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index f640d039ad31..679fa7e2b016 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -326,7 +326,7 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); - mBroadcastDispatcher.registerReceiver(this, filter, mReceiverHandler); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; updateMobileControllers(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 019ef3bca709..312c4ac75bfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -126,7 +126,8 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi IntentFilter filter = new IntentFilter(); filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED); filter.addAction(Intent.ACTION_USER_UNLOCKED); - broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, bgHandler, UserHandle.ALL); + broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler, + UserHandle.ALL); // TODO: re-register network callback on user change. mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 7758aba52918..31b9952afe94 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -88,7 +88,7 @@ public class ThemeOverlayController extends SystemUI { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { + mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java index 7cdba8607d86..cc6d607a60cf 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java @@ -83,17 +83,19 @@ public abstract class ConcurrencyModule { * Provide a Background-Thread Executor by default. */ @Provides + @Singleton public static Executor provideExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Background-Thread Executor. */ @Provides + @Singleton @Background public static Executor provideBackgroundExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** @@ -109,26 +111,29 @@ public abstract class ConcurrencyModule { * Provide a Background-Thread Executor by default. */ @Provides + @Singleton public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Background-Thread Executor. */ @Provides + @Singleton @Background public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Main-Thread Executor. */ @Provides + @Singleton @Main public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java index 7e7732135e3a..2bbf9507122a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java @@ -17,37 +17,69 @@ package com.android.systemui.util.concurrency; import android.os.Handler; -import android.os.HandlerExecutor; +import android.os.Looper; import android.os.Message; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; /** * Implementations of {@link DelayableExecutor} for SystemUI. */ -public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor { +public class ExecutorImpl implements DelayableExecutor { private final Handler mHandler; - public ExecutorImpl(Handler handler) { - super(handler); - mHandler = handler; + ExecutorImpl(Looper looper) { + mHandler = new Handler(looper, this::onHandleMessage); + } + + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } } @Override public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) { - Object token = new Object(); - Message m = mHandler.obtainMessage(0, token); + ExecutionToken token = new ExecutionToken(r); + Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token); mHandler.sendMessageDelayed(m, unit.toMillis(delay)); - return () -> mHandler.removeCallbacksAndMessages(token); + return token; } @Override public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) { - Object token = new Object(); - Message m = mHandler.obtainMessage(0, token); + ExecutionToken token = new ExecutionToken(r); + Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token); mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis)); - return () -> mHandler.removeCallbacksAndMessages(token); + return token; + } + + private boolean onHandleMessage(Message msg) { + if (msg.what == MSG_EXECUTE_RUNNABLE) { + ExecutionToken token = (ExecutionToken) msg.obj; + token.runnable.run(); + } else { + throw new IllegalStateException("Unrecognized message: " + msg.what); + } + return true; } + + private class ExecutionToken implements Runnable { + public final Runnable runnable; + + private ExecutionToken(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public void run() { + mHandler.removeCallbacksAndMessages(this); + } + } + + private static final int MSG_EXECUTE_RUNNABLE = 0; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index a4ed31d95b2b..112ae6f3758a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -1008,7 +1008,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - mBroadcastDispatcher.registerReceiver(this, filter, mWorker); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker); } public void destroy() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 0c22aaeaafb9..2e0fb3bc850d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -150,10 +150,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testReceiversRegistered() { - verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver( + verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler( eq(mKeyguardUpdateMonitor.mBroadcastReceiver), any(IntentFilter.class), any(Handler.class)); - verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver( + verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler( eq(mKeyguardUpdateMonitor.mBroadcastAllReceiver), any(IntentFilter.class), any(Handler.class), eq(UserHandle.ALL)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt index 42fbf59fef44..22b18373e81d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt @@ -27,6 +27,8 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertSame import org.junit.Before import org.junit.Test @@ -39,6 +41,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.concurrent.Executor @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @@ -73,6 +76,8 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Mock private lateinit var mockHandler: Handler + private lateinit var executor: Executor + @Captor private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData> @@ -83,6 +88,7 @@ class BroadcastDispatcherTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + executor = FakeExecutor(FakeSystemClock()) broadcastDispatcher = TestBroadcastDispatcher( mockContext, @@ -98,8 +104,9 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Test fun testAddingReceiverToCorrectUBR() { - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0) - broadcastDispatcher.registerReceiver( + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user0) + broadcastDispatcher.registerReceiverWithHandler( broadcastReceiverOther, intentFilterOther, mockHandler, user1) testableLooper.processAllMessages() @@ -115,9 +122,29 @@ class BroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testAddingReceiverToCorrectUBR_executor() { + broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0) + broadcastDispatcher.registerReceiver( + broadcastReceiverOther, intentFilterOther, executor, user1) + + testableLooper.processAllMessages() + + verify(mockUBRUser0).registerReceiver(capture(argumentCaptor)) + + assertSame(broadcastReceiver, argumentCaptor.value.receiver) + assertSame(intentFilter, argumentCaptor.value.filter) + + verify(mockUBRUser1).registerReceiver(capture(argumentCaptor)) + assertSame(broadcastReceiverOther, argumentCaptor.value.receiver) + assertSame(intentFilterOther, argumentCaptor.value.filter) + } + + @Test fun testRemovingReceiversRemovesFromAllUBR() { - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0) - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user0) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user1) broadcastDispatcher.unregisterReceiver(broadcastReceiver) @@ -129,8 +156,10 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Test fun testRemoveReceiverFromUser() { - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0) - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user0) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user1) broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0) @@ -143,8 +172,8 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Test fun testRegisterCurrentAsActualUser() { setUserMock(mockContext, user1) - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, - UserHandle.CURRENT) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, UserHandle.CURRENT) testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt index 21ed15517752..7821ae29592e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt @@ -26,6 +26,8 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue @@ -69,8 +71,6 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context @Mock - private lateinit var mockHandler: Handler - @Mock private lateinit var mPendingResult: BroadcastReceiver.PendingResult @Captor @@ -81,12 +81,14 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { private lateinit var intentFilter: IntentFilter private lateinit var intentFilterOther: IntentFilter private lateinit var handler: Handler + private lateinit var fakeExecutor: FakeExecutor @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) handler = Handler(testableLooper.looper) + fakeExecutor = FakeExecutor(FakeSystemClock()) userBroadcastDispatcher = UserBroadcastDispatcher( mockContext, USER_ID, handler, testableLooper.looper) @@ -108,7 +110,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) testableLooper.processAllMessages() assertTrue(userBroadcastDispatcher.isRegistered()) @@ -128,7 +130,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) testableLooper.processAllMessages() reset(mockContext) @@ -151,9 +153,9 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) testableLooper.processAllMessages() assertTrue(userBroadcastDispatcher.isRegistered()) @@ -179,14 +181,15 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilterOther = IntentFilter(ACTION_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_2) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver, never()).onReceive(any(), any()) verify(broadcastReceiverOther).onReceive(mockContext, intent) @@ -198,14 +201,15 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilterOther = IntentFilter(ACTION_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_2) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver).onReceive(mockContext, intent) } @@ -218,14 +222,15 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilterOther.addCategory(CATEGORY_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_1) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver).onReceive(mockContext, intent) verify(broadcastReceiverOther).onReceive(mockContext, intent) @@ -235,12 +240,13 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { fun testPendingResult() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_1) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver).onReceive(mockContext, intent) verify(broadcastReceiver).pendingResult = mPendingResult @@ -250,15 +256,16 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { fun testRemoveReceiverReferences() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) intentFilterOther = IntentFilter(ACTION_1) intentFilterOther.addAction(ACTION_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.unregisterReceiver(broadcastReceiver) testableLooper.processAllMessages() + fakeExecutor.runAllReady() assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index 167f361b341a..548da8e1f2aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -110,7 +110,7 @@ public class PowerUITest extends SysuiTestCase { @Test public void testReceiverIsRegisteredToDispatcherOnStart() { mPowerUI.start(); - verify(mBroadcastDispatcher).registerReceiver( + verify(mBroadcastDispatcher).registerReceiverWithHandler( any(BroadcastReceiver.class), any(IntentFilter.class), any(Handler.class)); //PowerUI does not call with User diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 3fdbd3edfcaa..77659df738c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -47,9 +47,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; @@ -75,7 +72,6 @@ public class NotificationTestHelper { public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser()); private static final String GROUP_KEY = "gruKey"; - private static final String APP_NAME = "appName"; private final Context mContext; private int mId; @@ -307,6 +303,9 @@ public class NotificationTestHelper { null /* root */, false /* attachToRoot */); ExpandableNotificationRow row = mRow; + row.setGroupManager(mGroupManager); + row.setHeadsUpManager(mHeadsUpManager); + row.setAboveShelfChangedListener(aboveShelf -> {}); final NotificationChannel channel = new NotificationChannel( @@ -330,23 +329,6 @@ public class NotificationTestHelper { entry.setRow(row); entry.createIcons(mContext, entry.getSbn()); row.setEntry(entry); - - NotificationContentInflater contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mock(NotificationRemoteInputManager.class)); - contentBinder.setInflateSynchronously(true); - - row.initialize( - APP_NAME, - entry.getKey(), - mock(ExpansionLogger.class), - mock(KeyguardBypassController.class), - mGroupManager, - mHeadsUpManager, - contentBinder, - mock(OnExpandClickListener.class)); - row.setAboveShelfChangedListener(aboveShelf -> { }); - row.setInflationFlags(extraInflationFlags); inflateAndWait(row); @@ -359,6 +341,7 @@ public class NotificationTestHelper { private static void inflateAndWait(ExpandableNotificationRow row) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); + row.getNotificationInflater().setInflateSynchronously(true); NotificationContentInflater.InflationCallback callback = new NotificationContentInflater.InflationCallback() { @Override 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 1c294531ea68..cd33cf922482 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 @@ -83,14 +83,12 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -236,7 +234,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mock(NotificationFilter.class), mNotifLog, mock(NotificationSectionsFeatureManager.class), - mock(PeopleNotificationIdentifier.class)), + mock(PeopleNotificationIdentifier.class), + mock(HighPriorityProvider.class)), mEnvironment, mFeatureFlags ); @@ -244,14 +243,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.addNotificationEntryListener(mEntryListener); mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); - NotificationRowContentBinder contentBinder = new NotificationContentInflater( - mock(NotifRemoteViewCache.class), - mRemoteInputManager); - NotificationRowBinderImpl notificationRowBinder = - new NotificationRowBinderImpl(mContext, - contentBinder, - true, /* allowLongPress */ + new NotificationRowBinderImpl(mContext, true, /* allowLongPress */ mock(KeyguardBypassController.class), mock(StatusBarStateController.class), mock(NotificationLogger.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java deleted file mode 100644 index a06d6c1e593b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 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.systemui.statusbar.notification.collection; - -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.RankingBuilder; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class GroupEntryTest extends SysuiTestCase { - @Test - public void testIsHighPriority_addChild() { - // GIVEN a GroupEntry with a lowPrioritySummary and no children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPrioritySummary = createNotifEntry(false); - setSummary(parentEntry, lowPrioritySummary); - assertFalse(parentEntry.isHighPriority()); - - // WHEN we add a high priority child and invalidate derived members - addChild(parentEntry, createNotifEntry(true)); - parentEntry.invalidateDerivedMembers(); - - // THEN the GroupEntry's priority is updated to high even though the summary is still low - // priority - assertTrue(parentEntry.isHighPriority()); - assertFalse(lowPrioritySummary.isHighPriority()); - } - - @Test - public void testIsHighPriority_clearChildren() { - // GIVEN a GroupEntry with a lowPrioritySummary and high priority children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(true)); - addChild(parentEntry, createNotifEntry(true)); - addChild(parentEntry, createNotifEntry(true)); - assertTrue(parentEntry.isHighPriority()); - - // WHEN we clear the children and invalidate derived members - parentEntry.clearChildren(); - parentEntry.invalidateDerivedMembers(); - - // THEN the parentEntry isn't high priority anymore - assertFalse(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_summaryUpdated() { - // GIVEN a GroupEntry with a lowPrioritySummary and no children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPrioritySummary = createNotifEntry(false); - setSummary(parentEntry, lowPrioritySummary); - assertFalse(parentEntry.isHighPriority()); - - // WHEN the summary changes to high priority and invalidates its derived members - lowPrioritySummary.setRanking( - new RankingBuilder() - .setKey(lowPrioritySummary.getKey()) - .setImportance(IMPORTANCE_HIGH) - .build()); - lowPrioritySummary.invalidateDerivedMembers(); - assertTrue(lowPrioritySummary.isHighPriority()); - - // THEN the GroupEntry's priority is updated to high - assertTrue(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_checkChildrenToCalculatePriority() { - // GIVEN: - // GroupEntry = parentEntry, summary = lowPrioritySummary - // NotificationEntry = lowPriorityChild - // NotificationEntry = highPriorityChild - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(true)); - - // THEN the GroupEntry parentEntry is high priority since it has a high priority child - assertTrue(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_childEntryRankingUpdated() { - // GIVEN: - // GroupEntry = parentEntry, summary = lowPrioritySummary - // NotificationEntry = lowPriorityChild - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPriorityChild = createNotifEntry(false); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, lowPriorityChild); - - // WHEN the child entry ranking changes to high priority and invalidates its derived members - lowPriorityChild.setRanking( - new RankingBuilder() - .setKey(lowPriorityChild.getKey()) - .setImportance(IMPORTANCE_HIGH) - .build()); - lowPriorityChild.invalidateDerivedMembers(); - - // THEN the parent entry's high priority value is updated - but not the parent's summary - assertTrue(parentEntry.isHighPriority()); - assertFalse(parentEntry.getSummary().isHighPriority()); - } - - private NotificationEntry createNotifEntry(boolean highPriority) { - return new NotificationEntryBuilder() - .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN) - .build(); - } - - private void setSummary(GroupEntry parent, NotificationEntry summary) { - parent.setSummary(summary); - summary.setParent(parent); - } - - private void addChild(GroupEntry parent, NotificationEntry child) { - parent.addChild(child); - child.setParent(parent); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java new file mode 100644 index 000000000000..93909dc4d330 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -0,0 +1,225 @@ +/* + * 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.collection; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MIN; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class HighPriorityProviderTest extends SysuiTestCase { + @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; + private HighPriorityProvider mHighPriorityProvider; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier); + } + + @Test + public void highImportance() { + // GIVEN notification has high importance + final NotificationEntry entry = new NotificationEntryBuilder() + .setImportance(IMPORTANCE_HIGH) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void peopleNotification() { + // GIVEN notification is low importance and is a people notification + final Notification notification = new Notification.Builder(mContext, "test") + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void messagingStyle() { + // GIVEN notification is low importance but has messaging style + final Notification notification = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void lowImportanceForeground() { + // GIVEN notification is low importance and is associated with a foreground service + final Notification notification = mock(Notification.class); + when(notification.isForegroundService()).thenReturn(true); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void minImportanceForeground() { + // GIVEN notification is low importance and is associated with a foreground service + final Notification notification = mock(Notification.class); + when(notification.isForegroundService()).thenReturn(true); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_MIN) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it does NOT have high priority + assertFalse(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void userChangeTrumpsHighPriorityCharacteristics() { + // GIVEN notification has high priority characteristics but the user changed the importance + // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN) + final Notification notification = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + final NotificationChannel channel = new NotificationChannel("a", "a", + IMPORTANCE_LOW); + channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setChannel(channel) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + + // THEN it does NOT have high priority + assertFalse(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void testIsHighPriority_summaryUpdated() { + // GIVEN a GroupEntry with a lowPrioritySummary and no children + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + final NotificationEntry lowPrioritySummary = createNotifEntry(false); + setSummary(parentEntry, lowPrioritySummary); + assertFalse(mHighPriorityProvider.isHighPriority(parentEntry)); + + // WHEN the summary changes to high priority + lowPrioritySummary.setRanking( + new RankingBuilder() + .setKey(lowPrioritySummary.getKey()) + .setImportance(IMPORTANCE_HIGH) + .build()); + assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary)); + + // THEN the GroupEntry's priority is updated to high + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + } + + @Test + public void testIsHighPriority_checkChildrenToCalculatePriority() { + // GIVEN: + // GroupEntry = parentEntry, summary = lowPrioritySummary + // NotificationEntry = lowPriorityChild + // NotificationEntry = highPriorityChild + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + setSummary(parentEntry, createNotifEntry(false)); + addChild(parentEntry, createNotifEntry(false)); + addChild(parentEntry, createNotifEntry(true)); + + // THEN the GroupEntry parentEntry is high priority since it has a high priority child + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + } + + @Test + public void testIsHighPriority_childEntryRankingUpdated() { + // GIVEN: + // GroupEntry = parentEntry, summary = lowPrioritySummary + // NotificationEntry = lowPriorityChild + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + final NotificationEntry lowPriorityChild = createNotifEntry(false); + setSummary(parentEntry, createNotifEntry(false)); + addChild(parentEntry, lowPriorityChild); + + // WHEN the child entry ranking changes to high priority + lowPriorityChild.setRanking( + new RankingBuilder() + .setKey(lowPriorityChild.getKey()) + .setImportance(IMPORTANCE_HIGH) + .build()); + + // THEN the parent entry's high priority value is updated - but not the parent's summary + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary())); + } + + private NotificationEntry createNotifEntry(boolean highPriority) { + return new NotificationEntryBuilder() + .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN) + .build(); + } + + private void setSummary(GroupEntry parent, NotificationEntry summary) { + parent.setSummary(summary); + summary.setParent(parent); + } + + private void addChild(GroupEntry parent, NotificationEntry child) { + parent.addChild(child); + child.setParent(parent); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 39ae68a40291..5b0b66849027 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -21,8 +21,6 @@ import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; @@ -92,44 +90,6 @@ public class NotificationEntryTest extends SysuiTestCase { } @Test - public void testIsHighPriority_notificationUpdates() { - // GIVEN a notification with high importance - final NotificationEntry entryHigh = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // WHEN we get the value for the high priority entry, we're caching the high priority value - assertTrue(entryHigh.isHighPriority()); - - // WHEN we change the ranking and derived members (high priority) are invalidated - entryHigh.setRanking( - new RankingBuilder() - .setKey(entryHigh.getKey()) - .setImportance(IMPORTANCE_MIN) - .build()); - entryHigh.invalidateDerivedMembers(); - - // THEN the priority is recalculated and is now low - assertFalse(entryHigh.isHighPriority()); - - // WHEN the sbn is updated to have messaging style (high priority characteristic) - // AND the entry invalidates its derived members - final Notification notification = - new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - final StatusBarNotification sbn = entryHigh.getSbn(); - entryHigh.setSbn(new StatusBarNotification( - sbn.getPackageName(), sbn.getPackageName(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0)); - entryHigh.invalidateDerivedMembers(); - - // THEN the priority is recalculated and is now high - assertTrue(entryHigh.isHighPriority()); - } - - @Test public void testIsExemptFromDndVisualSuppression_foreground() { mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index 10450fa82cde..e27319103525 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING @@ -62,7 +63,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { mock(NotificationFilter::class.java), mock(NotifLog::class.java), mock(NotificationSectionsFeatureManager::class.java), - personNotificationIdentifier + personNotificationIdentifier, + HighPriorityProvider(personNotificationIdentifier) ) } @@ -182,7 +184,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { filter: NotificationFilter, notifLog: NotifLog, sectionsFeatureManager: NotificationSectionsFeatureManager, - peopleNotificationIdentifier: PeopleNotificationIdentifier + peopleNotificationIdentifier: PeopleNotificationIdentifier, + highPriorityProvider: HighPriorityProvider ) : NotificationRankingManager( mediaManager, groupManager, @@ -190,7 +193,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { filter, notifLog, sectionsFeatureManager, - peopleNotificationIdentifier + peopleNotificationIdentifier, + highPriorityProvider ) { fun applyTestRankingMap(r: RankingMap) { rankingMap = r diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java index 979b8a906ef0..f921cf969e61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.collection.NotifListBuilderIm import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; @@ -66,6 +67,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private NotifListBuilderImpl mNotifListBuilder; private NotificationEntry mEntry; @@ -78,7 +80,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { mKeyguardCoordinator = new KeyguardCoordinator( mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager, mBroadcastDispatcher, mStatusBarStateController, - mKeyguardUpdateMonitor); + mKeyguardUpdateMonitor, mHighPriorityProvider); mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java deleted file mode 100644 index 6fa1a89515c3..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 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.systemui.statusbar.notification.collection.provider; - -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.Person; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class IsHighPriorityProviderTest extends SysuiTestCase { - private IsHighPriorityProvider mIsHighPriorityProvider; - - @Before - public void setup() { - mIsHighPriorityProvider = new IsHighPriorityProvider(); - } - - @Test - public void testCache() { - // GIVEN a notification with high importance - final NotificationEntry entryHigh = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // GIVEN notification with min importance - final NotificationEntry entryMin = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_MIN) - .build(); - - // WHEN we get the value for the high priority entry - assertTrue(mIsHighPriorityProvider.get(entryHigh)); - - // THEN the value is cached, so even when passed an entryMin, we still get high priority - assertTrue(mIsHighPriorityProvider.get(entryMin)); - - // UNTIL the provider is invalidated - mIsHighPriorityProvider.invalidate(); - - // THEN the priority is recalculated - assertFalse(mIsHighPriorityProvider.get(entryMin)); - } - - @Test - public void highImportance() { - // GIVEN notification has high importance - final NotificationEntry entry = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void peopleNotification() { - // GIVEN notification is low importance but has a person associated with it - final Notification notification = new Notification.Builder(mContext, "test") - .addPerson( - new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build()) - .build(); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_LOW) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void messagingStyle() { - // GIVEN notification is low importance but has messaging style - final Notification notification = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void lowImportanceForeground() { - // GIVEN notification is low importance and is associated with a foreground service - final Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_LOW) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void minImportanceForeground() { - // GIVEN notification is low importance and is associated with a foreground service - final Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_MIN) - .build(); - - // THEN it does NOT have high priority - assertFalse(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void userChangeTrumpsHighPriorityCharacteristics() { - // GIVEN notification has high priority characteristics but the user changed the importance - // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN) - final Notification notification = new Notification.Builder(mContext, "test") - .addPerson( - new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build()) - .setStyle(new Notification.MessagingStyle("")) - .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) - .build(); - - final NotificationChannel channel = new NotificationChannel("a", "a", - IMPORTANCE_LOW); - channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setChannel(channel) - .build(); - - // THEN it does NOT have high priority - assertFalse(mIsHighPriorityProvider.get(entry)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java deleted file mode 100644 index d7214f3b9228..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.row; - -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; -import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.testing.AndroidTestingRunner; -import android.widget.RemoteViews; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.NotificationEntryListener; -import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class NotifRemoteViewCacheImplTest extends SysuiTestCase { - - private NotifRemoteViewCacheImpl mNotifRemoteViewCache; - private NotificationEntry mEntry; - private NotificationEntryListener mEntryListener; - @Mock private RemoteViews mRemoteViews; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mEntry = new NotificationEntryBuilder().build(); - - NotificationEntryManager entryManager = mock(NotificationEntryManager.class); - mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager); - ArgumentCaptor<NotificationEntryListener> entryListenerCaptor = - ArgumentCaptor.forClass(NotificationEntryListener.class); - verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture()); - mEntryListener = entryListenerCaptor.getValue(); - } - - @Test - public void testPutCachedView() { - // GIVEN an initialized cache for an entry. - mEntryListener.onPendingEntryAdded(mEntry); - - // WHEN a notification's cached remote views is put in. - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); - - // THEN the remote view is cached. - assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - assertEquals( - "Cached remote view is not the one we put in.", - mRemoteViews, - mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - } - - @Test - public void testRemoveCachedView() { - // GIVEN a cache with a cached view. - mEntryListener.onPendingEntryAdded(mEntry); - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); - - // WHEN we remove the cached view. - mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED); - - // THEN the remote view is not cached. - assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - } - - @Test - public void testClearCache() { - // GIVEN a non-empty cache. - mEntryListener.onPendingEntryAdded(mEntry); - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews); - mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews); - - // WHEN we clear the cache. - mNotifRemoteViewCache.clearCache(mEntry); - - // THEN the cache is empty. - assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED)); - assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index cb9da6a40cb9..f916fe5ad7fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -22,16 +22,11 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.app.Notification; import android.content.Context; @@ -40,17 +35,16 @@ import android.os.Handler; import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.util.ArrayMap; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; -import android.widget.TextView; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.InflationTask; -import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams; @@ -63,8 +57,6 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import java.util.HashMap; import java.util.concurrent.CountDownLatch; @@ -81,11 +73,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { private Notification.Builder mBuilder; private ExpandableNotificationRow mRow; - @Mock private NotifRemoteViewCache mCache; - @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); mBuilder = new Notification.Builder(mContext).setSmallIcon( R.drawable.ic_person) .setContentTitle("Title") @@ -94,9 +83,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow( mBuilder.build()); mRow = spy(row); - mNotificationInflater = new NotificationContentInflater( - mCache, - mock(NotificationRemoteInputManager.class)); + mNotificationInflater = new NotificationContentInflater(); } @Test @@ -187,9 +174,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { result, FLAG_CONTENT_VIEW_EXPANDED, 0, - mock(NotifRemoteViewCache.class), - mRow.getEntry(), - mRow, + new ArrayMap() /* cachedContentViews */, mRow, true /* isNewView */, (v, p, r) -> true, new InflationCallback() { @Override @@ -259,71 +244,6 @@ public class NotificationContentInflaterTest extends SysuiTestCase { NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView)); } - @Test - public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception { - // GIVEN a cached view. - RemoteViews contractedRemoteView = mBuilder.createContentView(); - when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(true); - when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(contractedRemoteView); - - // GIVEN existing bound view with same layout id. - View view = contractedRemoteView.apply(mContext, null /* parent */); - mRow.getPrivateLayout().setContractedChild(view); - - // WHEN inflater inflates - inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); - - // THEN the view should be re-used - assertEquals("Binder inflated a new view even though the old one was cached and usable.", - view, mRow.getPrivateLayout().getContractedChild()); - } - - @Test - public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception { - // GIVEN a cached remote view. - RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView(); - when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(true); - when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED)) - .thenReturn(contractedRemoteView); - - // GIVEN existing bound view with different layout id. - View view = new TextView(mContext); - mRow.getPrivateLayout().setContractedChild(view); - - // WHEN inflater inflates - inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); - - // THEN the view should be a new view - assertNotEquals("Binder (somehow) used the same view when inflating.", - view, mRow.getPrivateLayout().getContractedChild()); - } - - @Test - public void testInflationCachesCreatedRemoteView() throws Exception { - // WHEN inflater inflates - inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow); - - // THEN inflater informs cache of the new remote view - verify(mCache).putCachedView( - eq(mRow.getEntry()), - eq(FLAG_CONTENT_VIEW_CONTRACTED), - any()); - } - - @Test - public void testUnbindRemovesCachedRemoteView() { - // WHEN inflated unbinds content - mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP); - - // THEN inflated informs cache to remove remote view - verify(mCache).removeCachedView( - eq(mRow.getEntry()), - eq(FLAG_CONTENT_VIEW_HEADS_UP)); - } - private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index ccc9496368e9..4e27770982e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -70,6 +70,7 @@ import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.StatusBar; @@ -113,6 +114,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private StatusBar mStatusBar; @Mock private AccessibilityManager mAccessibilityManager; + @Mock private HighPriorityProvider mHighPriorityProvider; @Before public void setUp() { @@ -128,7 +130,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager, - () -> mStatusBar, mHandler, mAccessibilityManager); + () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider); mGutsManager.setUpWithPresenter(mPresenter, mStackScroller, mCheckSaveListener, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); @@ -391,6 +393,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { .build(); when(row.getIsNonblockable()).thenReturn(false); + when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true); StatusBarNotification statusBarNotification = entry.getSbn(); mGutsManager.initializeNotificationInfo(row, notificationInfoView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index 003d80376c40..518b6703391e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -267,8 +267,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(notifRow.getVisibility()).thenReturn(View.VISIBLE); - when(notifRow.getEntry().isHighPriority()) - .thenReturn(children[i] == ChildType.HIPRI); when(notifRow.getEntry().getBucket()).thenReturn( children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT); when(notifRow.getParent()).thenReturn(mNssl); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 39f037cfa70d..ea8d4ee20aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -165,7 +166,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mock(NotificationFilter.class), mock(NotifLog.class), mock(NotificationSectionsFeatureManager.class), - mock(PeopleNotificationIdentifier.class) + mock(PeopleNotificationIdentifier.class), + mock(HighPriorityProvider.class) ), mock(NotificationEntryManager.KeyguardEnvironment.class), mock(FeatureFlags.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 39afbe0a1a23..8f645b6bd3c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -190,7 +190,7 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { mFragments.dispatchResume(); processAllMessages(); - verify(mBroadcastDispatcher).registerReceiver( + verify(mBroadcastDispatcher).registerReceiverWithHandler( any(BroadcastReceiver.class), any(IntentFilter.class), any(Handler.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 3da87ea2fb56..7e485f45e5e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -122,7 +122,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline; import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; @@ -248,7 +247,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private DismissCallbackRegistry mDismissCallbackRegistry; @Mock private ScreenPinningRequest mScreenPinningRequest; @Mock private NotificationEntryManager mEntryManager; - @Mock private NotificationContentInflater mNotificationContentInflater; @Mock private LockscreenLockIconController mLockscreenLockIconController; @Mock private StatusBarNotificationActivityStarter.Builder mStatusBarNotificationActivityStarterBuilder; @@ -358,7 +356,6 @@ public class StatusBarTest extends SysuiTestCase { mNotificationGutsManager, notificationLogger, mEntryManager, - mNotificationContentInflater, mNotificationInterruptionStateProvider, mNotificationViewHierarchyManager, mKeyguardViewMediator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 2854665aedb1..80aa6f6c49bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -69,7 +69,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { @Test public void testRegisteredWithDispatcher() { - verify(mBroadcastDispatcher).registerReceiver(any(BroadcastReceiver.class), + verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class), any(IntentFilter.class), any(Handler.class)); // VolumeDialogControllerImpl does not call with user } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 797b713d8614..d297f3f84189 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -123,4 +123,5 @@ android_app { use_embedded_native_libs: true, // The permission configuration *must* be included to ensure security of the device required: ["NetworkPermissionConfig"], + apex_available: ["com.android.tethering"], } diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml index 87a8c3f5c68a..e99c2c529bd2 100644 --- a/packages/Tethering/AndroidManifest.xml +++ b/packages/Tethering/AndroidManifest.xml @@ -33,6 +33,7 @@ <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> + <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp index 5785707cb9c5..264ce440f59f 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -47,6 +47,16 @@ java_library { libs: [ "android_system_stubs_current", ], + + hostdex: true, // for hiddenapi check + visibility: [ + "//frameworks/base/packages/Tethering:__subpackages__", + //TODO(b/147200698) remove below lines when the platform is built with stubs + "//frameworks/base", + "//frameworks/base/services", + "//frameworks/base/services/core", + ], + apex_available: ["com.android.tethering"], } filegroup { diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index fe3f51700df9..5692a6fc5c80 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,9 +16,12 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -35,8 +38,10 @@ import android.net.util.PrefixUtils; import android.net.util.SharedLog; import android.os.Handler; import android.util.Log; +import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.internal.util.StateMachine; import java.util.HashMap; @@ -77,11 +82,25 @@ public class UpstreamNetworkMonitor { public static final int EVENT_ON_LINKPROPERTIES = 2; public static final int EVENT_ON_LOST = 3; public static final int NOTIFY_LOCAL_PREFIXES = 10; + // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default + // disabled. + @VisibleForTesting + public static final int TYPE_NONE = -1; private static final int CALLBACK_LISTEN_ALL = 1; private static final int CALLBACK_DEFAULT_INTERNET = 2; private static final int CALLBACK_MOBILE_REQUEST = 3; + private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray(); + static { + sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH); + sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET); + } + private final Context mContext; private final SharedLog mLog; private final StateMachine mTarget; @@ -202,7 +221,7 @@ public class UpstreamNetworkMonitor { final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI; final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder() - .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType)) + .setCapabilities(networkCapabilitiesForType(legacyType)) .build(); // The existing default network and DUN callbacks will be notified. @@ -354,16 +373,6 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_LINKPROPERTIES, network); } - private void handleSuspended(Network network) { - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("SUSPENDED current upstream: " + network); - } - - private void handleResumed(Network network) { - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("RESUMED current upstream: " + network); - } - private void handleLost(Network network) { // There are few TODOs within ConnectivityService's rematching code // pertaining to spurious onLost() notifications. @@ -453,20 +462,6 @@ public class UpstreamNetworkMonitor { } @Override - public void onNetworkSuspended(Network network) { - if (mCallbackType == CALLBACK_LISTEN_ALL) { - handleSuspended(network); - } - } - - @Override - public void onNetworkResumed(Network network) { - if (mCallbackType == CALLBACK_LISTEN_ALL) { - handleResumed(network); - } - } - - @Override public void onLost(Network network) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = null; @@ -510,7 +505,7 @@ public class UpstreamNetworkMonitor { for (int type : preferredTypes) { NetworkCapabilities nc; try { - nc = ConnectivityManager.networkCapabilitiesForType(type); + nc = networkCapabilitiesForType(type); } catch (IllegalArgumentException iae) { Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type); continue; @@ -572,4 +567,28 @@ public class UpstreamNetworkMonitor { return null; } + + /** + * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance. + * This function is used for deprecated legacy type and be disabled by default. + */ + @VisibleForTesting + public static NetworkCapabilities networkCapabilitiesForType(int type) { + final NetworkCapabilities nc = new NetworkCapabilities(); + + // Map from type to transports. + final int notFound = -1; + final int transport = sLegacyTypeToTransport.get(type, notFound); + Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type); + nc.addTransportType(transport); + + if (type == TYPE_MOBILE_DUN) { + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN); + // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES. + nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + } else { + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + return nc; + } } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index c90abbbedb5f..5ed75bf26f8b 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -18,13 +18,14 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -538,13 +539,15 @@ public class UpstreamNetworkMonitorTest { mUNM.selectPreferredUpstreamType(preferredTypes)); verify(mEntitleMgr, times(1)).maybeRunProvisioning(); } + private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); return; } - final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType); + final NetworkCapabilities nc = + UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType); assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities)); } diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp index 2a94237f2aed..61f8143e68b5 100644 --- a/packages/services/PacProcessor/jni/Android.bp +++ b/packages/services/PacProcessor/jni/Android.bp @@ -37,4 +37,10 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], + sanitize: { + cfi: true, + diag: { + cfi: true, + }, + }, } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 1a6533d6e643..f34b5e71ad7b 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -324,7 +324,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + "mForAugmentedAutofillOnly: %s", mForAugmentedAutofillOnly); return; } - if (mCurrentViewId == null) { + // Keeps to prevent it is cleared on multiple threads. + final AutofillId currentViewId = mCurrentViewId; + if (currentViewId == null) { Slog.w(TAG, "No current view id - session might have finished"); return; } @@ -398,7 +400,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mContexts == null) { mContexts = new ArrayList<>(1); } - mContexts.add(new FillContext(requestId, structure, mCurrentViewId)); + mContexts.add(new FillContext(requestId, structure, currentViewId)); cancelCurrentRequestLocked(); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 3bce322a7655..d45a54e0ff28 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1452,6 +1452,11 @@ public class BackupManagerService extends IBackupManager.Stub { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { return; } + dumpWithoutCheckingPermission(fd, pw, args); + } + + @VisibleForTesting + void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) { int userId = binderGetCallingUserId(); if (!isUserReadyForBackup(userId)) { pw.println("Inactive"); @@ -1460,7 +1465,16 @@ public class BackupManagerService extends IBackupManager.Stub { if (args != null) { for (String arg : args) { - if ("users".equals(arg.toLowerCase())) { + if ("-h".equals(arg)) { + pw.println("'dumpsys backup' optional arguments:"); + pw.println(" -h : this help text"); + pw.println(" a[gents] : dump information about defined backup agents"); + pw.println(" transportclients : dump information about transport clients"); + pw.println(" transportstats : dump transport statts"); + pw.println(" users : dump the list of users for which backup service " + + "is running"); + return; + } else if ("users".equals(arg.toLowerCase())) { pw.print(DUMP_RUNNING_USERS_MESSAGE); for (int i = 0; i < mUserServices.size(); i++) { pw.print(" " + mUserServices.keyAt(i)); @@ -1471,11 +1485,12 @@ public class BackupManagerService extends IBackupManager.Stub { } } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()"); - - if (userBackupManagerService != null) { - userBackupManagerService.dump(fd, pw, args); + for (int i = 0; i < mUserServices.size(); i++) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()"); + if (userBackupManagerService != null) { + userBackupManagerService.dump(fd, pw, args); + } } } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 064cd060528d..7b95ab526b41 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -3545,14 +3545,7 @@ public class UserBackupManagerService { try { if (args != null) { for (String arg : args) { - if ("-h".equals(arg)) { - pw.println("'dumpsys backup' optional arguments:"); - pw.println(" -h : this help text"); - pw.println(" a[gents] : dump information about defined backup agents"); - pw.println(" users : dump the list of users for which backup service " - + "is running"); - return; - } else if ("agents".startsWith(arg)) { + if ("agents".startsWith(arg)) { dumpAgents(pw); return; } else if ("transportclients".equals(arg.toLowerCase())) { @@ -3583,8 +3576,10 @@ public class UserBackupManagerService { } private void dumpInternal(PrintWriter pw) { + // Add prefix for only non-system users so that system user dumpsys is the same as before + String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":"; synchronized (mQueueLock) { - pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled") + pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled") + " / " + (!mSetupComplete ? "not " : "") + "setup complete / " + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init"); pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled")); @@ -3594,13 +3589,13 @@ public class UserBackupManagerService { + " (now = " + System.currentTimeMillis() + ')'); pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId)); - pw.println("Transport whitelist:"); + pw.println(userPrefix + "Transport whitelist:"); for (ComponentName transport : mTransportManager.getTransportWhitelist()) { pw.print(" "); pw.println(transport.flattenToShortString()); } - pw.println("Available transports:"); + pw.println(userPrefix + "Available transports:"); final String[] transports = listAllTransports(); if (transports != null) { for (String t : transports) { @@ -3626,18 +3621,18 @@ public class UserBackupManagerService { mTransportManager.dumpTransportClients(pw); - pw.println("Pending init: " + mPendingInits.size()); + pw.println(userPrefix + "Pending init: " + mPendingInits.size()); for (String s : mPendingInits) { pw.println(" " + s); } - pw.print("Ancestral: "); + pw.print(userPrefix + "Ancestral: "); pw.println(Long.toHexString(mAncestralToken)); - pw.print("Current: "); + pw.print(userPrefix + "Current: "); pw.println(Long.toHexString(mCurrentToken)); int numPackages = mBackupParticipants.size(); - pw.println("Participants:"); + pw.println(userPrefix + "Participants:"); for (int i = 0; i < numPackages; i++) { int uid = mBackupParticipants.keyAt(i); pw.print(" uid: "); @@ -3648,7 +3643,7 @@ public class UserBackupManagerService { } } - pw.println("Ancestral packages: " + pw.println(userPrefix + "Ancestral packages: " + (mAncestralPackages == null ? "none" : mAncestralPackages.size())); if (mAncestralPackages != null) { for (String pkg : mAncestralPackages) { @@ -3657,17 +3652,17 @@ public class UserBackupManagerService { } Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy(); - pw.println("Ever backed up: " + processedPackages.size()); + pw.println(userPrefix + "Ever backed up: " + processedPackages.size()); for (String pkg : processedPackages) { pw.println(" " + pkg); } - pw.println("Pending key/value backup: " + mPendingBackups.size()); + pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size()); for (BackupRequest req : mPendingBackups.values()) { pw.println(" " + req); } - pw.println("Full backup queue:" + mFullBackupQueue.size()); + pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size()); for (FullBackupEntry entry : mFullBackupQueue) { pw.print(" "); pw.print(entry.lastBackup); diff --git a/services/core/Android.bp b/services/core/Android.bp index a1f57cb51188..b2fba730fac1 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -165,9 +165,3 @@ prebuilt_etc { name: "protolog.conf.json.gz", src: ":services.core.json.gz", } - -platform_compat_config { - name: "services-core-platform-compat-config", - src: ":services.core.unboosted", -} - diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 312dd46fbc73..76572d35d516 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -811,6 +811,12 @@ public abstract class PackageManagerInternal { public abstract boolean isApexPackage(String packageName); /** + * Returns list of {@code packageName} of apks inside the given apex. + * @param apexPackageName Package name of the apk container of apex + */ + public abstract List<String> getApksInApex(String apexPackageName); + + /** * Uninstalls given {@code packageName}. * * @param packageName apex package to uninstall. diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java index bbcfdc63f3f1..32cdc41472c9 100644 --- a/services/core/java/com/android/server/GnssManagerService.java +++ b/services/core/java/com/android/server/GnssManagerService.java @@ -47,7 +47,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocationManagerServiceUtils.LinkedListener; import com.android.server.LocationManagerServiceUtils.LinkedListenerBase; -import com.android.server.location.AbstractLocationProvider; import com.android.server.location.CallerIdentity; import com.android.server.location.GnssBatchingProvider; import com.android.server.location.GnssCapabilitiesProvider; @@ -116,11 +115,9 @@ public class GnssManagerService { private final Handler mHandler; public GnssManagerService(LocationManagerService locationManagerService, - Context context, - AbstractLocationProvider.LocationProviderManager gnssProviderManager, - LocationUsageLogger locationUsageLogger) { - this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager, - FgThread.getHandler().getLooper()), locationUsageLogger); + Context context, LocationUsageLogger locationUsageLogger) { + this(locationManagerService, context, + new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger); } // Can use this constructor to inject GnssLocationProvider for testing diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java index 70569db5e2d3..5179fa7a6eb5 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -38,11 +38,13 @@ import android.view.IGraphicsStats; import android.view.IGraphicsStatsCallback; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FastPrintWriter; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -78,6 +80,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private static final int SAVE_BUFFER = 1; private static final int DELETE_OLD = 2; + private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever. + // This isn't static because we need this to happen after registerNativeMethods, however // the class is loaded (and thus static ctor happens) before that occurs. private final int ASHMEM_SIZE = nGetAshmemSize(); @@ -121,6 +125,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return true; } }); + nativeInit(); } /** @@ -186,6 +191,86 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return pfd; } + // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period + // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the + // current day. + // This method is invoked from native code only. + @SuppressWarnings({"UnusedDeclaration"}) + private long pullGraphicsStats(boolean lastFullDay) throws RemoteException { + int uid = Binder.getCallingUid(); + + // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method. + // TODO: remove exception for statsd daemon after required permissions are granted. statsd + // TODO: should have these permissions granted by data/etc/platform.xml, but it does not. + if (uid != AID_STATSD) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { + pw.flush(); + throw new RemoteException(sw.toString()); + } + } + + long callingIdentity = Binder.clearCallingIdentity(); + try { + return pullGraphicsStatsImpl(lastFullDay); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + private long pullGraphicsStatsImpl(boolean lastFullDay) { + long targetDay; + if (lastFullDay) { + // Get stats from yesterday. Stats stay constant, because the day is over. + targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis(); + } else { + // Get stats from today. Stats may change as more apps are run today. + targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis(); + } + + // Find active buffers for targetDay. + ArrayList<HistoricalBuffer> buffers; + synchronized (mLock) { + buffers = new ArrayList<>(mActive.size()); + for (int i = 0; i < mActive.size(); i++) { + ActiveBuffer buffer = mActive.get(i); + if (buffer.mInfo.startTime == targetDay) { + try { + buffers.add(new HistoricalBuffer(buffer)); + } catch (IOException ex) { + // Ignore + } + } + } + } + + // Dump active and historic buffers for targetDay in a serialized + // GraphicsStatsServiceDumpProto proto. + long dump = nCreateDump(-1, true); + try { + synchronized (mFileAccessLock) { + HashSet<File> skipList = dumpActiveLocked(dump, buffers); + buffers.clear(); + String subPath = String.format("%d", targetDay); + File dateDir = new File(mGraphicsStatsDir, subPath); + if (dateDir.exists()) { + for (File pkg : dateDir.listFiles()) { + for (File version : pkg.listFiles()) { + File data = new File(version, "total"); + if (skipList.contains(data)) { + continue; + } + nAddToDump(dump, data.getAbsolutePath()); + } + } + } + } + } finally { + return nFinishDumpInMemory(dump); + } + } + private ParcelFileDescriptor getPfd(MemoryFile file) { try { if (!file.getFileDescriptor().valid()) { @@ -379,12 +464,21 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } + @Override + protected void finalize() throws Throwable { + nativeDestructor(); + } + + private native void nativeInit(); + private static native void nativeDestructor(); + private static native int nGetAshmemSize(); private static native long nCreateDump(int outFd, boolean isProto); private static native void nAddToDump(long dump, String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); private static native void nAddToDump(long dump, String path); private static native void nFinishDump(long dump); + private static native long nFinishDumpInMemory(long dump); private static native void nSaveBuffer(String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 0fc5340846f2..32128d5f26f8 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -23,8 +23,6 @@ import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.os.PowerManager.locationPowerSaveModeToString; -import static com.android.internal.util.Preconditions.checkState; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -74,7 +72,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; -import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.text.TextUtils; import android.util.EventLog; @@ -92,6 +89,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.location.AbstractLocationProvider; +import com.android.server.location.AbstractLocationProvider.State; import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.CallerIdentity; import com.android.server.location.GeocoderProxy; @@ -105,6 +103,7 @@ import com.android.server.location.LocationRequestStatistics.PackageStatistics; import com.android.server.location.LocationSettingsStore; import com.android.server.location.LocationUsageLogger; import com.android.server.location.MockProvider; +import com.android.server.location.MockableLocationProvider; import com.android.server.location.PassiveProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -121,6 +120,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; /** @@ -196,6 +197,8 @@ public class LocationManagerService extends ILocationManager.Stub { private final LocationSettingsStore mSettingsStore; private final LocationUsageLogger mLocationUsageLogger; + private final PassiveLocationProviderManager mPassiveManager; + private AppOpsManager mAppOps; private PackageManager mPackageManager; private PowerManager mPowerManager; @@ -205,21 +208,17 @@ public class LocationManagerService extends ILocationManager.Stub { private GeofenceManager mGeofenceManager; private LocationFudger mLocationFudger; private GeocoderProxy mGeocodeProvider; - @Nullable - private GnssManagerService mGnssManagerService; - private PassiveProvider mPassiveProvider; // track passive provider for special cases + @Nullable private GnssManagerService mGnssManagerService; + @GuardedBy("mLock") private String mExtraLocationControllerPackage; - private boolean mExtraLocationControllerPackageEnabled; - - // list of currently active providers @GuardedBy("mLock") - private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>(); + private boolean mExtraLocationControllerPackageEnabled; - // list of non-mock providers, so that when mock providers replace real providers, they can be - // later re-replaced - @GuardedBy("mLock") - private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>(); + // @GuardedBy("mLock") + // hold lock for write or to prevent write, no lock for read + private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = + new CopyOnWriteArrayList<>(); @GuardedBy("mLock") private final HashMap<Object, Receiver> mReceivers = new HashMap<>(); @@ -238,9 +237,9 @@ public class LocationManagerService extends ILocationManager.Stub { private final HashMap<String, Location> mLastLocationCoarseInterval = new HashMap<>(); - // current active user on the device - other users are denied location data - private int mCurrentUserId = UserHandle.USER_SYSTEM; - private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM}; + // current active user on the device + private int mCurrentUserId; + private int[] mCurrentUserProfiles; @GuardedBy("mLock") @PowerManager.LocationPowerSaveMode @@ -252,6 +251,17 @@ public class LocationManagerService extends ILocationManager.Stub { mSettingsStore = new LocationSettingsStore(mContext, mHandler); mLocationUsageLogger = new LocationUsageLogger(); + mCurrentUserId = UserHandle.USER_NULL; + mCurrentUserProfiles = new int[]{UserHandle.USER_NULL}; + + // set up passive provider - we do this early because it has no dependencies on system + // services or external code that isn't ready yet, and because this allows the variable to + // be final. other more complex providers are initialized later, when system services are + // ready + mPassiveManager = new PassiveLocationProviderManager(); + mProviderManagers.add(mPassiveManager); + mPassiveManager.setRealProvider(new PassiveProvider(mContext)); + // Let the package manager query which are the default location // providers as they get certain permissions granted by default. PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService( @@ -415,15 +425,15 @@ public class LocationManagerService extends ILocationManager.Stub { for (Receiver receiver : mReceivers.values()) { receiver.updateMonitoring(true); } - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onPermissionsChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -442,16 +452,16 @@ public class LocationManagerService extends ILocationManager.Stub { mBatterySaverMode = newLocationMode; - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onScreenStateChangedLocked() { if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } } @@ -466,8 +476,8 @@ public class LocationManagerService extends ILocationManager.Stub { intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId)); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(userId); + for (LocationProviderManager manager : mProviderManagers) { + manager.onUseableChangedLocked(userId); } } @@ -521,22 +531,22 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void onBackgroundThrottleIntervalChangedLocked() { - for (LocationProviderManager provider : mProviders) { - applyRequirementsLocked(provider); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onBackgroundThrottleWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("lock") private void onIgnoreSettingsWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -623,22 +633,11 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void initializeProvidersLocked() { - // create a passive location provider, which is always enabled - LocationProviderManager passiveProviderManager = new LocationProviderManager( - PASSIVE_PROVIDER); - addProviderLocked(passiveProviderManager); - mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager); - passiveProviderManager.attachLocked(mPassiveProvider); - if (GnssManagerService.isGnssSupported()) { - // Create a gps location provider manager - LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER); - mRealProviders.add(gnssProviderManager); - addProviderLocked(gnssProviderManager); - - mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager, - mLocationUsageLogger); - gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider()); + mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger); + LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER); + mProviderManagers.add(gnssManager); + gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider()); } /* @@ -662,37 +661,31 @@ public class LocationManagerService extends ILocationManager.Stub { ensureFallbackFusedProviderPresentLocked(pkgs); - // bind to network provider - LocationProviderManager networkProviderManager = new LocationProviderManager( - NETWORK_PROVIDER); LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext, - networkProviderManager, NETWORK_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableNetworkLocationOverlay, com.android.internal.R.string.config_networkLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (networkProvider != null) { - mRealProviders.add(networkProviderManager); - addProviderLocked(networkProviderManager); - networkProviderManager.attachLocked(networkProvider); + LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER); + mProviderManagers.add(networkManager); + networkManager.setRealProvider(networkProvider); } else { Slog.w(TAG, "no network location provider found"); } // bind to fused provider - LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER); LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( mContext, - fusedProviderManager, FUSED_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableFusedLocationOverlay, com.android.internal.R.string.config_fusedLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (fusedProvider != null) { - mRealProviders.add(fusedProviderManager); - addProviderLocked(fusedProviderManager); - fusedProviderManager.attachLocked(fusedProvider); + LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER); + mProviderManagers.add(fusedManager); + fusedManager.setRealProvider(fusedProvider); } else { Slog.e(TAG, "no fused location provider found", new IllegalStateException("Location service needs a fused location provider")); @@ -754,10 +747,7 @@ public class LocationManagerService extends ILocationManager.Stub { Boolean.parseBoolean(fragments[7]) /* supportsBearing */, Integer.parseInt(fragments[8]) /* powerRequirement */, Integer.parseInt(fragments[9]) /* accuracy */); - LocationProviderManager testProviderManager = new LocationProviderManager(name); - addProviderLocked(testProviderManager); - testProviderManager.attachLocked( - new MockProvider(mContext, testProviderManager, properties)); + addTestProvider(name, properties, mContext.getOpPackageName()); } } @@ -771,231 +761,202 @@ public class LocationManagerService extends ILocationManager.Stub { Log.d(TAG, "foreground user is changing to " + userId); } - int oldUserId = userId; + int oldUserId = mCurrentUserId; mCurrentUserId = userId; onUserProfilesChangedLocked(); // let providers know the current user has changed - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(oldUserId); - p.onUseableChangedLocked(mCurrentUserId); + for (LocationProviderManager manager : mProviderManagers) { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + mSettingsStore.setLocationProviderAllowed(manager.getName(), + manager.isUseable(mCurrentUserId), mCurrentUserId); + + manager.onUseableChangedLocked(oldUserId); + manager.onUseableChangedLocked(mCurrentUserId); } } /** * Location provider manager, manages a LocationProvider. */ - class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager { + class LocationProviderManager implements MockableLocationProvider.Listener { private final String mName; - // remember to clear binder identity before invoking any provider operation - @GuardedBy("mLock") - @Nullable - protected AbstractLocationProvider mProvider; + // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary + protected final MockableLocationProvider mProvider; @GuardedBy("mLock") - private SparseArray<Boolean> mUseable; // combined state for each user id - @GuardedBy("mLock") - private boolean mEnabled; // state of provider - - @GuardedBy("mLock") - @Nullable - private ProviderProperties mProperties; + private final SparseArray<Boolean> mUseable; // combined state for each user id private LocationProviderManager(String name) { mName = name; - - mProvider = null; mUseable = new SparseArray<>(1); - mEnabled = false; - mProperties = null; - - // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-" + mName, - mCurrentUserId); - } - - @GuardedBy("mLock") - public void attachLocked(AbstractLocationProvider provider) { - Objects.requireNonNull(provider); - checkState(mProvider == null); - if (D) { - Log.d(TAG, mName + " provider attached"); - } - - mProvider = provider; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - onUseableChangedLocked(mCurrentUserId); + // initialize last since this lets our reference escape + mProvider = new MockableLocationProvider(mContext, mLock, this); } public String getName() { return mName; } - @GuardedBy("mLock") - public List<String> getPackagesLocked() { - if (mProvider == null) { - return Collections.emptyList(); - } else { - // safe to not clear binder context since this doesn't call into the real provider - return mProvider.getProviderPackages(); - } + public boolean hasProvider() { + return mProvider.getProvider() != null; } - public boolean isMock() { - return false; + public void setRealProvider(AbstractLocationProvider provider) { + mProvider.setRealProvider(provider); } - @GuardedBy("mLock") - public boolean isPassiveLocked() { - return mProvider == mPassiveProvider; + public void setMockProvider(@Nullable MockProvider provider) { + mProvider.setMockProvider(provider); + } + + public Set<String> getPackages() { + return mProvider.getState().providerPackageNames; } - @GuardedBy("mLock") @Nullable - public ProviderProperties getPropertiesLocked() { - return mProperties; + public ProviderProperties getProperties() { + return mProvider.getState().properties; } - public void setRequest(ProviderRequest request, WorkSource workSource) { - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSetRequest(request, workSource); - } + public void setMockProviderEnabled(boolean enabled) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); } - }); + + mProvider.setMockProviderEnabled(enabled); + } } - public void sendExtraCommand(String command, Bundle extras) { - int uid = Binder.getCallingUid(); - int pid = Binder.getCallingPid(); + public void setMockProviderLocation(Location location) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); + } - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSendExtraCommand(uid, pid, command, extras); - } + String locationProvider = location.getProvider(); + if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) { + // The location has an explicit provider that is different from the mock + // provider name. The caller may be trying to fool us via b/33091107. + EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), + mName + "!=" + locationProvider); } - }); - } - @GuardedBy("mLock") - public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { - pw.print(mName + " provider"); - if (isMock()) { - pw.print(" [mock]"); + mProvider.setMockProviderLocation(location); } - pw.println(":"); + } - pw.increaseIndent(); + public List<LocationRequest> getMockProviderRequests() { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); + } - pw.println("useable=" + isUseableLocked(mCurrentUserId)); - if (!isUseableLocked(mCurrentUserId)) { - pw.println("attached=" + (mProvider != null)); - pw.println("enabled=" + mEnabled); + return mProvider.getCurrentRequest().locationRequests; } + } - pw.println("properties=" + mProperties); + public void setRequest(ProviderRequest request) { + mProvider.setRequest(request); + } - if (mProvider != null) { - // in order to be consistent with other provider APIs, this should be run on the - // location thread... but this likely isn't worth it just for dumping info. - long identity = Binder.clearCallingIdentity(); - try { - mProvider.dump(fd, pw, args); - } finally { - Binder.restoreCallingIdentity(identity); + public void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + + public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { + synchronized (mLock) { + pw.print(mName + " provider"); + if (mProvider.isMock()) { + pw.print(" [mock]"); + } + pw.println(":"); + + pw.increaseIndent(); + + pw.println("useable=" + isUseable(mCurrentUserId)); + if (!isUseable(mCurrentUserId)) { + pw.println("enabled=" + mProvider.getState().enabled); } + + pw.println("properties=" + mProvider.getState().properties); } + mProvider.dump(fd, pw, args); + pw.decreaseIndent(); } + @GuardedBy("mLock") @Override public void onReportLocation(Location location) { - // likelihood of a 0,0 bug is far greater than this being a valid location - if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) { - Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); - return; + // don't validate mock locations + if (!location.isFromMockProvider()) { + if (location.getLatitude() == 0 && location.getLongitude() == 0) { + Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); + return; + } } - synchronized (mLock) { - handleLocationChangedLocked(location, this); - } + handleLocationChangedLocked(location, this); } + @GuardedBy("mLock") @Override public void onReportLocation(List<Location> locations) { if (mGnssManagerService == null) { return; } - synchronized (mLock) { - LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER); - if (gpsProvider == null || !gpsProvider.isUseableLocked()) { - Slog.w(TAG, "reportLocationBatch() called without user permission"); - return; - } - mGnssManagerService.onReportLocation(locations); + if (!GPS_PROVIDER.equals(mName) || !isUseable()) { + Slog.w(TAG, "reportLocationBatch() called without user permission"); + return; } - } - @Override - public void onSetEnabled(boolean enabled) { - synchronized (mLock) { - if (enabled == mEnabled) { - return; - } - - if (D) { - Log.d(TAG, mName + " provider enabled is now " + mEnabled); - } - - mEnabled = enabled; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - onUseableChangedLocked(mCurrentUserId); - } + mGnssManagerService.onReportLocation(locations); } + @GuardedBy("mLock") @Override - public void onSetProperties(ProviderProperties properties) { - synchronized (mLock) { - mProperties = properties; + public void onStateChanged(State oldState, State newState) { + if (oldState.enabled != newState.enabled) { + // it would be more correct to call this for all users, but we know this can + // only affect the current user since providers are disabled for non-current + // users + onUseableChangedLocked(mCurrentUserId); } } @GuardedBy("mLock") - public boolean isUseableLocked() { - return isUseableLocked(mCurrentUserId); + public boolean isUseable() { + return isUseable(mCurrentUserId); } @GuardedBy("mLock") - public boolean isUseableLocked(int userId) { - return mUseable.get(userId, Boolean.FALSE); + public boolean isUseable(int userId) { + synchronized (mLock) { + return mUseable.get(userId, Boolean.FALSE); + } } @GuardedBy("mLock") public void onUseableChangedLocked(int userId) { + if (userId == UserHandle.USER_NULL) { + // only used during initialization - we don't care about the null user + return; + } + // if any property that contributes to "useability" here changes state, it MUST result // in a direct or indrect call to onUseableChangedLocked. this allows the provider to // guarantee that it will always eventually reach the correct state. - boolean useable = mProvider != null && mProviders.contains(this) - && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId) - && mEnabled; + boolean useable = isCurrentProfileLocked(userId) + && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled; - if (useable == isUseableLocked(userId)) { + if (useable == isUseable(userId)) { return; } mUseable.put(userId, useable); @@ -1007,11 +968,7 @@ public class LocationManagerService extends ILocationManager.Stub { // fused and passive provider never get public updates for legacy reasons if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) { // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - (useable ? "+" : "-") + mName, - userId); + mSettingsStore.setLocationProviderAllowed(mName, useable, userId); Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION); intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName); @@ -1031,53 +988,38 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private class MockLocationProvider extends LocationProviderManager { + class PassiveLocationProviderManager extends LocationProviderManager { - private ProviderRequest mCurrentRequest; - - private MockLocationProvider(String name) { - super(name); + private PassiveLocationProviderManager() { + super(PASSIVE_PROVIDER); } @Override - public void attachLocked(AbstractLocationProvider provider) { - checkState(provider instanceof MockProvider); - super.attachLocked(provider); - } - - public boolean isMock() { - return true; + public void setRealProvider(AbstractLocationProvider provider) { + Preconditions.checkArgument(provider instanceof PassiveProvider); + super.setRealProvider(provider); } - @GuardedBy("mLock") - public void setEnabledLocked(boolean enabled) { - if (mProvider != null) { - long identity = Binder.clearCallingIdentity(); - try { - ((MockProvider) mProvider).setEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); - } + @Override + public void setMockProvider(@Nullable MockProvider provider) { + if (provider != null) { + throw new IllegalArgumentException("Cannot mock the passive provider"); } } - @GuardedBy("mLock") - public void setLocationLocked(Location location) { - if (mProvider != null) { + public void updateLocation(Location location) { + synchronized (mLock) { + PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider(); + Preconditions.checkState(passiveProvider != null); + long identity = Binder.clearCallingIdentity(); try { - ((MockProvider) mProvider).setLocation(location); + passiveProvider.updateLocation(location); } finally { Binder.restoreCallingIdentity(identity); } } } - - @Override - public void setRequest(ProviderRequest request, WorkSource workSource) { - super.setRequest(request, workSource); - mCurrentRequest = request; - } } /** @@ -1181,17 +1123,17 @@ public class LocationManagerService extends ILocationManager.Stub { // See if receiver has any enabled update records. Also note if any update records // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { - LocationProviderManager provider = getLocationProviderLocked( + LocationProviderManager manager = getLocationProviderManager( updateRecord.mProvider); - if (provider == null) { + if (manager == null) { continue; } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) { + if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) { continue; } requestingLocation = true; - ProviderProperties properties = provider.getPropertiesLocked(); + ProviderProperties properties = manager.getProperties(); if (properties != null && properties.mPowerRequirement == Criteria.POWER_HIGH && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { @@ -1432,7 +1374,7 @@ public class LocationManagerService extends ILocationManager.Stub { String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback( + return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback( callback, packageName, featureId, listenerIdentifier); } @@ -1443,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) { - return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos, + return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, packageName); } @@ -1454,35 +1396,14 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean stopGnssBatch() { - return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch(); + return mGnssManagerService != null && mGnssManagerService.stopGnssBatch(); } - @GuardedBy("mLock") - private void addProviderLocked(LocationProviderManager provider) { - Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null); - - mProviders.add(provider); - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - - @GuardedBy("mLock") - private void removeProviderLocked(LocationProviderManager provider) { - if (mProviders.remove(provider)) { - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - } - - @GuardedBy("mLock") @Nullable - private LocationProviderManager getLocationProviderLocked(String providerName) { - for (LocationProviderManager provider : mProviders) { - if (providerName.equals(provider.getName())) { - return provider; + private LocationProviderManager getLocationProviderManager(String providerName) { + for (LocationProviderManager manager : mProviderManagers) { + if (providerName.equals(manager.getName())) { + return manager; } } @@ -1531,12 +1452,12 @@ public class LocationManagerService extends ILocationManager.Stub { // network and fused providers are ok with COARSE or FINE return RESOLUTION_LEVEL_COARSE; } else { - for (LocationProviderManager lp : mProviders) { + for (LocationProviderManager lp : mProviderManagers) { if (!lp.getName().equals(provider)) { continue; } - ProviderProperties properties = lp.getPropertiesLocked(); + ProviderProperties properties = lp.getProperties(); if (properties != null) { if (properties.mRequiresSatellite) { // provider requiring satellites require FINE permission @@ -1587,11 +1508,9 @@ public class LocationManagerService extends ILocationManager.Stub { case RESOLUTION_LEVEL_COARSE: return AppOpsManager.OPSTR_COARSE_LOCATION; case RESOLUTION_LEVEL_FINE: - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through case RESOLUTION_LEVEL_NONE: - // The client is not allowed to get any location, so both FINE and COARSE ops will - // be denied. Pick the most restrictive one to be safe. - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through default: // Use the most restrictive ops if not sure. return AppOpsManager.OPSTR_FINE_LOCATION; @@ -1629,17 +1548,14 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public List<String> getAllProviders() { - synchronized (mLock) { - ArrayList<String> providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); - if (FUSED_PROVIDER.equals(name)) { - continue; - } - providers.add(name); + ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + if (FUSED_PROVIDER.equals(manager.getName())) { + continue; } - return providers; + providers.add(manager.getName()); } + return providers; } /** @@ -1651,21 +1567,21 @@ public class LocationManagerService extends ILocationManager.Stub { public List<String> getProviders(Criteria criteria, boolean enabledOnly) { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); synchronized (mLock) { - ArrayList<String> providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); + ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + String name = manager.getName(); if (FUSED_PROVIDER.equals(name)) { continue; } if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) { continue; } - if (enabledOnly && !provider.isUseableLocked()) { + if (enabledOnly && !manager.isUseable()) { continue; } if (criteria != null && !android.location.LocationProvider.propertiesMeetCriteria( - name, provider.getPropertiesLocked(), criteria)) { + name, manager.getProperties(), criteria)) { continue; } providers.add(name); @@ -1702,12 +1618,12 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void updateProviderUseableLocked(LocationProviderManager provider) { - boolean useable = provider.isUseableLocked(); + private void updateProviderUseableLocked(LocationProviderManager manager) { + boolean useable = manager.isUseable(); ArrayList<Receiver> deadReceivers = null; - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); if (records != null) { for (UpdateRecord record : records) { if (!isCurrentProfileLocked( @@ -1721,7 +1637,7 @@ public class LocationManagerService extends ILocationManager.Stub { } // Sends a notification message to the receiver - if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { + if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) { if (deadReceivers == null) { deadReceivers = new ArrayList<>(); } @@ -1736,26 +1652,25 @@ public class LocationManagerService extends ILocationManager.Stub { } } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } @GuardedBy("mLock") private void applyRequirementsLocked(String providerName) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - applyRequirementsLocked(provider); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") - private void applyRequirementsLocked(LocationProviderManager provider) { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); - WorkSource worksource = new WorkSource(); - ProviderRequest providerRequest = new ProviderRequest(); + private void applyRequirementsLocked(LocationProviderManager manager) { + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); + ProviderRequest.Builder providerRequest = new ProviderRequest.Builder(); // if provider is not active, it should not respond to requests - if (mProviders.contains(provider) && records != null && !records.isEmpty()) { + if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) { long backgroundThrottleInterval; long identity = Binder.clearCallingIdentity(); @@ -1765,6 +1680,8 @@ public class LocationManagerService extends ILocationManager.Stub { Binder.restoreCallingIdentity(identity); } + ArrayList<LocationRequest> requests = new ArrayList<>(records.size()); + final boolean isForegroundOnlyMode = mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY; final boolean shouldThrottleRequests = @@ -1772,7 +1689,7 @@ public class LocationManagerService extends ILocationManager.Stub { == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF && !mPowerManager.isInteractive(); // initialize the low power mode to true and set to false if any of the records requires - providerRequest.lowPowerMode = true; + providerRequest.setLowPowerMode(true); for (UpdateRecord record : records) { if (!isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1787,10 +1704,10 @@ public class LocationManagerService extends ILocationManager.Stub { } final boolean isBatterySaverDisablingLocation = shouldThrottleRequests || (isForegroundOnlyMode && !record.mIsForegroundUid); - if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) { + if (!manager.isUseable() || isBatterySaverDisablingLocation) { if (isSettingsExemptLocked(record)) { - providerRequest.locationSettingsIgnored = true; - providerRequest.lowPowerMode = false; + providerRequest.setLocationSettingsIgnored(true); + providerRequest.setLowPowerMode(false); } else { continue; } @@ -1801,7 +1718,7 @@ public class LocationManagerService extends ILocationManager.Stub { // if we're forcing location, don't apply any throttling - if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked( + if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked( record.mReceiver.mCallerIdentity)) { if (!record.mIsForegroundUid) { interval = Math.max(interval, backgroundThrottleInterval); @@ -1813,23 +1730,25 @@ public class LocationManagerService extends ILocationManager.Stub { } record.mRequest = locationRequest; - providerRequest.locationRequests.add(locationRequest); + requests.add(locationRequest); if (!locationRequest.isLowPowerMode()) { - providerRequest.lowPowerMode = false; + providerRequest.setLowPowerMode(false); } - if (interval < providerRequest.interval) { - providerRequest.reportLocation = true; - providerRequest.interval = interval; + if (interval < providerRequest.getInterval()) { + providerRequest.setInterval(interval); } } - if (providerRequest.reportLocation) { + providerRequest.setLocationRequests(requests); + + if (providerRequest.getInterval() < Long.MAX_VALUE) { // calculate who to blame for power // This is somewhat arbitrary. We pick a threshold interval // that is slightly higher that the minimum interval, and // spread the blame across all applications with a request // under that threshold. - long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; + // TODO: overflow + long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2; for (UpdateRecord record : records) { if (isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1837,18 +1756,18 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't assign battery blame for update records whose // client has no permission to receive location data. - if (!providerRequest.locationRequests.contains(locationRequest)) { + if (!providerRequest.getLocationRequests().contains(locationRequest)) { continue; } if (locationRequest.getInterval() <= thresholdInterval) { if (record.mReceiver.mWorkSource != null && isValidWorkSource(record.mReceiver.mWorkSource)) { - worksource.add(record.mReceiver.mWorkSource); + providerRequest.getWorkSource().add(record.mReceiver.mWorkSource); } else { // Assign blame to caller if there's no WorkSource associated with // the request or if it's invalid. - worksource.add( + providerRequest.getWorkSource().add( record.mReceiver.mCallerIdentity.mUid, record.mReceiver.mCallerIdentity.mPackageName); } @@ -1858,7 +1777,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - provider.setRequest(providerRequest, worksource); + manager.setRequest(providerRequest.build()); } /** @@ -2198,8 +2117,8 @@ public class LocationManagerService extends ILocationManager.Stub { throw new IllegalArgumentException("provider name must not be null"); } - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) { + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) { throw new IllegalArgumentException("provider doesn't exist: " + name); } @@ -2217,7 +2136,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) { + if (!manager.isUseable() && !isSettingsExemptLocked(record)) { // Notify the listener that updates are currently disabled - but only if the request // does not ignore location settings receiver.callProviderEnabledLocked(name, false); @@ -2320,8 +2239,8 @@ public class LocationManagerService extends ILocationManager.Stub { // or use the fused provider String name = request.getProvider(); if (name == null) name = LocationManager.FUSED_PROVIDER; - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) return null; + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) return null; // only the current user or location providers may get location this way if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage( @@ -2329,7 +2248,7 @@ public class LocationManagerService extends ILocationManager.Stub { return null; } - if (!provider.isUseableLocked()) { + if (!manager.isUseable()) { return null; } @@ -2450,19 +2369,19 @@ public class LocationManagerService extends ILocationManager.Stub { "Access Fine Location permission not granted to inject Location"); synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(location.getProvider()); - if (provider == null || !provider.isUseableLocked()) { + LocationProviderManager manager = getLocationProviderManager(location.getProvider()); + if (manager == null || !manager.isUseable()) { return false; } // NOTE: If last location is already available, location is not injected. If // provider's normal source (like a GPS chipset) have already provided an output // there is no need to inject this location. - if (mLastLocation.get(provider.getName()) != null) { + if (mLastLocation.get(manager.getName()) != null) { return false; } - updateLastLocationLocked(location, provider.getName()); + updateLastLocationLocked(location, manager.getName()); return true; } } @@ -2511,7 +2430,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, request, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2542,7 +2461,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, /* LocationRequest= */ null, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2555,7 +2474,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName, String featureId) { - return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback( + return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback( listener, packageName, featureId); } @@ -2569,9 +2488,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId, - listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2586,8 +2504,8 @@ public class LocationManagerService extends ILocationManager.Stub { public void injectGnssMeasurementCorrections( GnssMeasurementCorrections measurementCorrections, String packageName) { if (mGnssManagerService != null) { - mGnssManagerService.injectGnssMeasurementCorrections( - measurementCorrections, packageName); + mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections, + packageName); } } @@ -2602,9 +2520,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName, - featureId, listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2634,9 +2551,10 @@ public class LocationManagerService extends ILocationManager.Stub { LocationStatsEnums.API_SEND_EXTRA_COMMAND, providerName); - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - provider.sendExtraCommand(command, extras); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command, + extras); } mLocationUsageLogger.logLocationApiUsage( @@ -2650,43 +2568,37 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean sendNiResponse(int notifId, int userResponse) { - return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId, + return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId, userResponse); } @Override public ProviderProperties getProviderProperties(String providerName) { - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider == null) { - return null; - } - return provider.getPropertiesLocked(); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager == null) { + return null; } + return manager.getProperties(); } @Override public boolean isProviderPackage(String packageName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - for (LocationProviderManager provider : mProviders) { - if (provider.getPackagesLocked().contains(packageName)) { - return true; - } + for (LocationProviderManager manager : mProviderManagers) { + if (manager.getPackages().contains(packageName)) { + return true; } - return false; } + return false; } @Override public List<String> getProviderPackages(String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider == null ? Collections.emptyList() : provider.getPackagesLocked(); - } + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages()); } @Override @@ -2753,8 +2665,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (FUSED_PROVIDER.equals(providerName)) return false; synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider != null && provider.isUseableLocked(userId); + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager != null && manager.isUseable(userId); } } @@ -2792,37 +2704,39 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void handleLocationChangedLocked(Location location, LocationProviderManager provider) { - if (!mProviders.contains(provider)) { + private void handleLocationChangedLocked(Location location, LocationProviderManager manager) { + if (!mProviderManagers.contains(manager)) { + Log.w(TAG, "received location from unknown provider: " + manager.getName()); return; } if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); + Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: " + + location); return; } - // only notify passive provider and update last location for locations that come from - // useable providers - if (provider.isUseableLocked()) { - if (!provider.isPassiveLocked()) { - mPassiveProvider.updateLocation(location); - } + // notify passive provider + if (manager != mPassiveManager) { + mPassiveManager.updateLocation(new Location(location)); } if (D) Log.d(TAG, "incoming location: " + location); long now = SystemClock.elapsedRealtime(); - if (provider.isUseableLocked()) { - updateLastLocationLocked(location, provider.getName()); + + + // only update last location for locations that come from useable providers + if (manager.isUseable()) { + updateLastLocationLocked(location, manager.getName()); } // Update last known coarse interval location if enough time has passed. Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get( - provider.getName()); + manager.getName()); if (lastLocationCoarseInterval == null) { lastLocationCoarseInterval = new Location(location); - if (provider.isUseableLocked()) { - mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); + if (manager.isUseable()) { + mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval); } } long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos() @@ -2837,7 +2751,7 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); if (records == null || records.size() == 0) return; // Fetch coarse location @@ -2854,7 +2768,7 @@ public class LocationManagerService extends ILocationManager.Stub { Receiver receiver = r.mReceiver; boolean receiverDead = false; - if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) { + if (!manager.isUseable() && !isSettingsExemptLocked(r)) { continue; } @@ -2949,7 +2863,7 @@ public class LocationManagerService extends ILocationManager.Stub { for (UpdateRecord r : deadUpdateRecords) { r.disposeLocked(true); } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } } @@ -3006,143 +2920,99 @@ public class LocationManagerService extends ILocationManager.Stub { // Mock Providers - private boolean canCallerAccessMockLocation(String opPackageName) { - return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), - opPackageName) == AppOpsManager.MODE_ALLOWED; - } - @Override - public void addTestProvider(String name, ProviderProperties properties, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void addTestProvider(String provider, ProviderProperties properties, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - if (PASSIVE_PROVIDER.equals(name)) { - throw new IllegalArgumentException("Cannot mock the passive location provider"); - } - synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager oldProvider = getLocationProviderLocked(name); - if (oldProvider != null) { - removeProviderLocked(oldProvider); - } - - MockLocationProvider mockProviderManager = new MockLocationProvider(name); - addProviderLocked(mockProviderManager); - mockProviderManager.attachLocked( - new MockProvider(mContext, mockProviderManager, properties)); - } finally { - Binder.restoreCallingIdentity(identity); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + manager = new LocationProviderManager(provider); + mProviderManagers.add(manager); } + + manager.setMockProvider(new MockProvider(mContext, properties)); } } @Override - public void removeTestProvider(String name, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void removeTestProvider(String provider, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager testProvider = getLocationProviderLocked(name); - if (testProvider == null || !testProvider.isMock()) { - return; - } - - removeProviderLocked(testProvider); - - // reinstate real provider if available - LocationProviderManager realProvider = null; - for (LocationProviderManager provider : mRealProviders) { - if (name.equals(provider.getName())) { - realProvider = provider; - break; - } - } + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + return; + } - if (realProvider != null) { - addProviderLocked(realProvider); - } - } finally { - Binder.restoreCallingIdentity(identity); + manager.setMockProvider(null); + if (!manager.hasProvider()) { + mProviderManagers.remove(manager); + mLastLocation.remove(manager.getName()); + mLastLocationCoarseInterval.remove(manager.getName()); } } } @Override - public void setTestProviderLocation(String providerName, Location location, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderLocation(String provider, Location location, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - String locationProvider = location.getProvider(); - if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) { - // The location has an explicit provider that is different from the mock - // provider name. The caller may be trying to fool us via b/33091107. - EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), - providerName + "!=" + location.getProvider()); - } - - ((MockLocationProvider) testProvider).setLocationLocked(location); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + manager.setMockProviderLocation(location); } @Override - public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderEnabled(String provider, boolean enabled, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - ((MockLocationProvider) testProvider).setEnabledLocked(enabled); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + manager.setMockProviderEnabled(enabled); } @Override @NonNull - public List<LocationRequest> getTestProviderCurrentRequests(String providerName, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public List<LocationRequest> getTestProviderCurrentRequests(String provider, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return Collections.emptyList(); } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - MockLocationProvider provider = (MockLocationProvider) testProvider; - if (provider.mCurrentRequest == null) { - return Collections.emptyList(); - } - List<LocationRequest> requests = new ArrayList<>(); - for (LocationRequest request : provider.mCurrentRequest.locationRequests) { - requests.add(new LocationRequest(request)); - } - return requests; + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + return manager.getMockProviderRequests(); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); @@ -3226,25 +3096,27 @@ public class LocationManagerService extends ILocationManager.Stub { mLocationFudger.dump(fd, ipw, args); ipw.decreaseIndent(); } + } - ipw.println("Location Settings:"); - ipw.increaseIndent(); - mSettingsStore.dump(fd, ipw, args); - ipw.decreaseIndent(); + ipw.println("Location Settings:"); + ipw.increaseIndent(); + mSettingsStore.dump(fd, ipw, args); + ipw.decreaseIndent(); - ipw.println("Location Providers:"); - ipw.increaseIndent(); - for (LocationProviderManager provider : mProviders) { - provider.dumpLocked(fd, ipw, args); - } - ipw.decreaseIndent(); + ipw.println("Location Providers:"); + ipw.increaseIndent(); + for (LocationProviderManager manager : mProviderManagers) { + manager.dump(fd, ipw, args); } + ipw.decreaseIndent(); - if (mGnssManagerService != null) { - ipw.println("GNSS:"); - ipw.increaseIndent(); - mGnssManagerService.dump(fd, ipw, args); - ipw.decreaseIndent(); + synchronized (mLock) { + if (mGnssManagerService != null) { + ipw.println("GNSS:"); + ipw.increaseIndent(); + mGnssManagerService.dump(fd, ipw, args); + ipw.decreaseIndent(); + } } } } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 3e6ccb572860..0be21c5f15b6 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -2194,6 +2194,17 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private static final String PHONE_CONSTANTS_STATE_KEY = "state"; private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription"; + /** + * Broadcast Action: The phone's signal strength has changed. The intent will have the + * following extra values: + * phoneName - A string version of the phone name. + * asu - A numeric value for the signal strength. + * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu). + * The following special values are defined: + * 0 means "-113 dBm or less".31 means "-51 dBm or greater". + */ + public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR"; + private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) { long ident = Binder.clearCallingIdentity(); try { @@ -2228,7 +2239,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { Binder.restoreCallingIdentity(ident); } - Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED); + Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED); Bundle data = new Bundle(); fillInSignalStrengthNotifierBundle(signalStrength, data); intent.putExtras(data); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index a58bd9bf32ff..e2a036aec172 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2068,7 +2068,7 @@ public final class ActiveServices { + " type=" + resolvedType + " callingUid=" + callingUid); userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false, - ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", + ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service", callingPackage); ServiceMap smap = getServiceMapLocked(userId); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index eb1ab3863624..2b207827a5ef 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -16,12 +16,14 @@ package com.android.server.am; +import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; import static android.app.ActivityManager.USER_OP_SUCCESS; +import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; @@ -1641,9 +1643,10 @@ class UserController implements Handler.Callback { if (callingUid != 0 && callingUid != SYSTEM_UID) { final boolean allow; + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId); if (mInjector.isCallerRecents(callingUid) && callingUserId == getCurrentUserId() - && isSameProfileGroup(callingUserId, targetUserId)) { + && isSameProfileGroup) { // If the caller is Recents and it is running in the current user, we then allow it // to access its profiles. allow = true; @@ -1654,6 +1657,9 @@ class UserController implements Handler.Callback { } else if (allowMode == ALLOW_FULL_ONLY) { // We require full access, sucks to be you. allow = false; + } else if (canInteractWithAcrossProfilesPermission( + allowMode, isSameProfileGroup, callingPid, callingUid)) { + allow = true; } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) { // If the caller does not have either permission, they are always doomed. @@ -1661,10 +1667,11 @@ class UserController implements Handler.Callback { } else if (allowMode == ALLOW_NON_FULL) { // We are blanket allowing non-full access, you lucky caller! allow = true; - } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) { + } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE + || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { // We may or may not allow this depending on whether the two users are // in the same profile. - allow = isSameProfileGroup(callingUserId, targetUserId); + allow = isSameProfileGroup; } else { throw new IllegalArgumentException("Unknown mode: " + allowMode); } @@ -1690,6 +1697,11 @@ class UserController implements Handler.Callback { if (allowMode != ALLOW_FULL_ONLY) { builder.append(" or "); builder.append(INTERACT_ACROSS_USERS); + if (isSameProfileGroup + && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + builder.append(" or "); + builder.append(INTERACT_ACROSS_PROFILES); + } } String msg = builder.toString(); Slog.w(TAG, msg); @@ -1710,6 +1722,19 @@ class UserController implements Handler.Callback { return targetUserId; } + private boolean canInteractWithAcrossProfilesPermission( + int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) { + if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + return false; + } + if (!isSameProfileGroup) { + return false; + } + return mInjector.checkComponentPermission( + INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1, + /*exported= */true) == PackageManager.PERMISSION_GRANTED; + } + int unsafeConvertIncomingUser(@UserIdInt int userId) { return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) ? getCurrentUserId(): userId; diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java index 31d4816e5d66..17a4b9c6c170 100644 --- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java +++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java @@ -39,6 +39,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -153,14 +154,19 @@ public class IntegrityFileManager { throws IOException, RuleParseException { synchronized (RULES_LOCK) { // Try to identify indexes from the index file. - List<RuleIndexRange> ruleReadingIndexes = - mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata); + List<RuleIndexRange> ruleReadingIndexes; + try { + ruleReadingIndexes = + mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata); + } catch (Exception e) { + Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e); + ruleReadingIndexes = Collections.emptyList(); + } - // Read the rules based on the index information. - // TODO(b/145493956): Provide the identified indexes to the rule reader. + // Read the rules based on the index information when available. try (FileInputStream inputStream = new FileInputStream(new File(mRulesDir, RULES_FILE))) { - List<Rule> rules = mRuleParser.parse(inputStream); + List<Rule> rules = mRuleParser.parse(inputStream, ruleReadingIndexes); return rules; } } diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java new file mode 100644 index 000000000000..e555e3e5746e --- /dev/null +++ b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java @@ -0,0 +1,69 @@ +/* + * 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.integrity.model; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An input stream that tracks the total number read bytes since construction and allows moving + * fast forward to a certain byte any time during the execution. + * + * This class is used for efficient reading of rules based on the rule indexing. + */ +public class BitTrackedInputStream extends BitInputStream { + + private static int sReadBitsCount; + + /** Constructor with byte array. */ + public BitTrackedInputStream(byte[] inputStream) { + super(inputStream); + sReadBitsCount = 0; + } + + /** Constructor with input stream. */ + public BitTrackedInputStream(InputStream inputStream) { + super(inputStream); + sReadBitsCount = 0; + } + + /** Obtains an integer value of the next {@code numOfBits}. */ + @Override + public int getNext(int numOfBits) throws IOException { + sReadBitsCount += numOfBits; + return super.getNext(numOfBits); + } + + /** Returns the current cursor position showing the number of bits that are read. */ + public int getReadBitsCount() { + return sReadBitsCount; + } + + /** + * Sets the cursor to the specified byte location. + * + * Note that the integer parameter specifies the location in bytes -- not bits. + */ + public void setCursorToByteLocation(int byteLocation) throws IOException { + int bitCountToRead = byteLocation * 8 - sReadBitsCount; + if (bitCountToRead < 0) { + throw new IllegalStateException("The byte position is already read."); + } + super.getNext(bitCountToRead); + sReadBitsCount = byteLocation * 8; + } +} diff --git a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java index 62815a9806ee..f575599e1c49 100644 --- a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java +++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.integrity.serializer; +package com.android.server.integrity.model; import java.io.IOException; import java.io.OutputStream; diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index cbb6e4e8e06f..e744326c49db 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -37,71 +37,103 @@ import android.content.integrity.CompoundFormula; import android.content.integrity.Formula; import android.content.integrity.Rule; -import com.android.server.integrity.model.BitInputStream; +import com.android.server.integrity.model.BitTrackedInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** A helper class to parse rules into the {@link Rule} model from Binary representation. */ public class RuleBinaryParser implements RuleParser { - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - @Override public List<Rule> parse(byte[] ruleBytes) throws RuleParseException { try { - BitInputStream bitInputStream = new BitInputStream(ruleBytes); - return parseRules(bitInputStream); + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes); + return parseRules(bitTrackedInputStream, /* indexRanges= */ Collections.emptyList()); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); } } @Override - public List<Rule> parse(InputStream inputStream) throws RuleParseException { + public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + throws RuleParseException { try { - BitInputStream bitInputStream = new BitInputStream(inputStream); - return parseRules(bitInputStream); + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream); + return parseRules(bitTrackedInputStream, indexRanges); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); } } - private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException { - List<Rule> parsedRules = new ArrayList<>(); + private List<Rule> parseRules( + BitTrackedInputStream bitTrackedInputStream, + List<RuleIndexRange> indexRanges) + throws IOException { // Read the rule binary file format version. - bitInputStream.getNext(FORMAT_VERSION_BITS); + bitTrackedInputStream.getNext(FORMAT_VERSION_BITS); + + return indexRanges.isEmpty() + ? parseAllRules(bitTrackedInputStream) + : parseIndexedRules(bitTrackedInputStream, indexRanges); + } + + private List<Rule> parseAllRules(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + List<Rule> parsedRules = new ArrayList<>(); + + while (bitTrackedInputStream.hasNext()) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(bitTrackedInputStream)); + } + } + + return parsedRules; + } + + private List<Rule> parseIndexedRules( + BitTrackedInputStream bitTrackedInputStream, List<RuleIndexRange> indexRanges) + throws IOException { + List<Rule> parsedRules = new ArrayList<>(); + + for (RuleIndexRange range : indexRanges) { + // Skip the rules that are not in the range. + bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex()); - while (bitInputStream.hasNext()) { - if (bitInputStream.getNext(SIGNAL_BIT) == 1) { - parsedRules.add(parseRule(bitInputStream)); + // Read the rules until we reach the end index. + while (bitTrackedInputStream.hasNext() + && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(bitTrackedInputStream)); + } } } return parsedRules; } - private Rule parseRule(BitInputStream bitInputStream) throws IOException { - Formula formula = parseFormula(bitInputStream); - int effect = bitInputStream.getNext(EFFECT_BITS); + private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException { + Formula formula = parseFormula(bitTrackedInputStream); + int effect = bitTrackedInputStream.getNext(EFFECT_BITS); - if (bitInputStream.getNext(SIGNAL_BIT) != 1) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) != 1) { throw new IllegalArgumentException("A rule must end with a '1' bit."); } return new Rule(formula, effect); } - private Formula parseFormula(BitInputStream bitInputStream) throws IOException { - int separator = bitInputStream.getNext(SEPARATOR_BITS); + private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException { + int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS); switch (separator) { case ATOMIC_FORMULA_START: - return parseAtomicFormula(bitInputStream); + return parseAtomicFormula(bitTrackedInputStream); case COMPOUND_FORMULA_START: - return parseCompoundFormula(bitInputStream); + return parseCompoundFormula(bitTrackedInputStream); case COMPOUND_FORMULA_END: return null; default: @@ -110,37 +142,40 @@ public class RuleBinaryParser implements RuleParser { } } - private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException { - int connector = bitInputStream.getNext(CONNECTOR_BITS); + private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS); List<Formula> formulas = new ArrayList<>(); - Formula parsedFormula = parseFormula(bitInputStream); + Formula parsedFormula = parseFormula(bitTrackedInputStream); while (parsedFormula != null) { formulas.add(parsedFormula); - parsedFormula = parseFormula(bitInputStream); + parsedFormula = parseFormula(bitTrackedInputStream); } return new CompoundFormula(connector, formulas); } - private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException { - int key = bitInputStream.getNext(KEY_BITS); - int operator = bitInputStream.getNext(OPERATOR_BITS); + private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + int key = bitTrackedInputStream.getNext(KEY_BITS); + int operator = bitTrackedInputStream.getNext(OPERATOR_BITS); switch (key) { case AtomicFormula.PACKAGE_NAME: case AtomicFormula.APP_CERTIFICATE: case AtomicFormula.INSTALLER_NAME: case AtomicFormula.INSTALLER_CERTIFICATE: - boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; - int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); - String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue); + boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1; + int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS); + String stringValue = getStringValue(bitTrackedInputStream, valueSize, + isHashedValue); return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue); case AtomicFormula.VERSION_CODE: - int intValue = getIntValue(bitInputStream); + int intValue = getIntValue(bitTrackedInputStream); return new AtomicFormula.IntAtomicFormula(key, operator, intValue); case AtomicFormula.PRE_INSTALLED: - boolean booleanValue = getBooleanValue(bitInputStream); + boolean booleanValue = getBooleanValue(bitTrackedInputStream); return new AtomicFormula.BooleanAtomicFormula(key, booleanValue); default: throw new IllegalArgumentException(String.format("Unknown key: %d", key)); diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java index 81783d5c7324..a8e9f6134759 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java @@ -28,5 +28,6 @@ public interface RuleParser { List<Rule> parse(byte[] ruleBytes) throws RuleParseException; /** Parse rules from an input stream. */ - List<Rule> parse(InputStream inputStream) throws RuleParseException; + List<Rule> parse(InputStream inputStream, List<RuleIndexRange> ruleIndexRanges) + throws RuleParseException; } diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java index d405583442bd..497be8424286 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java @@ -62,7 +62,8 @@ public final class RuleXmlParser implements RuleParser { } @Override - public List<Rule> parse(InputStream inputStream) throws RuleParseException { + public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + throws RuleParseException { try { XmlPullParser xmlPullParser = Xml.newPullParser(); xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name()); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index b8791c3c3489..f964d4cf2724 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -42,6 +42,7 @@ import android.content.integrity.Rule; import com.android.internal.util.Preconditions; import com.android.server.integrity.IntegrityUtils; import com.android.server.integrity.model.BitOutputStream; +import com.android.server.integrity.model.ByteTrackedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -109,7 +110,8 @@ public class RuleBinarySerializer implements RuleSerializer { } private void serializeRuleFileMetadata(Optional<Integer> formatVersion, - ByteTrackedOutputStream outputStream) throws IOException { + ByteTrackedOutputStream outputStream) + throws IOException { int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION); BitOutputStream bitOutputStream = new BitOutputStream(); diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index ccfc98e2291b..ed6a759409d4 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -16,11 +16,11 @@ package com.android.server.location; +import android.annotation.Nullable; import android.content.Context; import android.location.Location; import android.os.Binder; import android.os.Bundle; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -29,127 +29,336 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.UnaryOperator; /** - * Location Manager's interface for location providers. Always starts as disabled. + * Base class for all location providers. * * @hide */ public abstract class AbstractLocationProvider { /** - * Interface for communicating from a location provider back to the location service. + * Interface for listening to location providers. */ - public interface LocationProviderManager { + public interface Listener { /** - * May be called to inform the location service of a change in this location provider's - * enabled/disabled state. + * Called when a provider's state changes. May be invoked from any thread. Will be + * invoked with a cleared binder identity. */ - void onSetEnabled(boolean enabled); + void onStateChanged(State oldState, State newState); /** - * May be called to inform the location service of a change in this location provider's - * properties. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ - void onSetProperties(ProviderProperties properties); + void onReportLocation(Location location); /** - * May be called to inform the location service that this provider has a new location - * available. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ - void onReportLocation(Location location); + void onReportLocation(List<Location> locations); + } + + /** + * Holds a representation of the public state of a provider. + */ + public static final class State { /** - * May be called to inform the location service that this provider has a new location - * available. + * Default state value for a location provider that is disabled with no properties and an + * empty provider package list. */ - void onReportLocation(List<Location> locations); + public static final State EMPTY_STATE = new State(false, null, + Collections.emptySet()); + + /** + * The provider's enabled state. + */ + public final boolean enabled; + + /** + * The provider's properties. + */ + @Nullable public final ProviderProperties properties; + + /** + * The provider's package name list - provider packages may be afforded special privileges. + */ + public final Set<String> providerPackageNames; + + private State(boolean enabled, ProviderProperties properties, + Set<String> providerPackageNames) { + this.enabled = enabled; + this.properties = properties; + this.providerPackageNames = Objects.requireNonNull(providerPackageNames); + } + + private State withEnabled(boolean enabled) { + if (enabled == this.enabled) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProperties(ProviderProperties properties) { + if (properties.equals(this.properties)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProviderPackageNames(Set<String> providerPackageNames) { + if (providerPackageNames.equals(this.providerPackageNames)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof State)) { + return false; + } + State state = (State) o; + return enabled == state.enabled && properties == state.properties + && providerPackageNames.equals(state.providerPackageNames); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, properties, providerPackageNames); + } + } + + // combines listener and state information so that they can be updated atomically with respect + // to each other and an ordering established. + private static class InternalState { + @Nullable public final Listener listener; + public final State state; + + private InternalState(@Nullable Listener listener, State state) { + this.listener = listener; + this.state = state; + } + + private InternalState withListener(Listener listener) { + if (listener == this.listener) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(State state) { + if (state.equals(this.state)) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(UnaryOperator<State> operator) { + return withState(operator.apply(state)); + } } protected final Context mContext; - private final LocationProviderManager mLocationProviderManager; + protected final Executor mExecutor; + + // we use a lock-free implementation to update state to ensure atomicity between updating the + // provider state and setting the listener, so that the state updates a listener sees are + // consistent with when the listener was set (a listener should not see any updates that occur + // before it was set, and should not miss any updates that occur after it was set). + private final AtomicReference<InternalState> mInternalState; - protected AbstractLocationProvider( - Context context, LocationProviderManager locationProviderManager) { + protected AbstractLocationProvider(Context context, Executor executor) { + this(context, executor, Collections.singleton(context.getPackageName())); + } + + protected AbstractLocationProvider(Context context, Executor executor, + Set<String> packageNames) { mContext = context; - mLocationProviderManager = locationProviderManager; + mExecutor = executor; + mInternalState = new AtomicReference<>( + new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames))); } /** - * Call this method to report a change in provider enabled/disabled status. May be called from - * any thread. + * Sets the listener and returns the state at the moment the listener was set. The listener can + * expect to receive all state updates from after this point. */ - protected void setEnabled(boolean enabled) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); + State setListener(@Nullable Listener listener) { + return mInternalState.updateAndGet( + internalState -> internalState.withListener(listener)).state; + } + + /** + * Retrieves the state of the provider. + */ + State getState() { + return mInternalState.get().state; + } + + /** + * Sets the state of the provider to the new state. + */ + void setState(State newState) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(newState)); + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void setState(UnaryOperator<State> operator) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(operator)); + + // recreate the new state from our knowledge of the old state - unfortunately may result in + // an extra allocation, but oh well... + State newState = operator.apply(oldInternalState.state); + + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Call this method to report a change in provider properties. May be called from - * any thread. + * The current enabled state of this provider. + */ + protected boolean isEnabled() { + return mInternalState.get().state.enabled; + } + + /** + * The current provider properties of this provider. + */ + @Nullable + protected ProviderProperties getProperties() { + return mInternalState.get().state.properties; + } + + /** + * The current package set of this provider. + */ + protected Set<String> getProviderPackages() { + return mInternalState.get().state.providerPackageNames; + } + + /** + * Call this method to report a change in provider enabled/disabled status. + */ + protected void setEnabled(boolean enabled) { + setState(state -> state.withEnabled(enabled)); + } + + /** + * Call this method to report a change in provider properties. */ protected void setProperties(ProviderProperties properties) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetProperties(properties); - } finally { - Binder.restoreCallingIdentity(identity); - } + setState(state -> state.withProperties(properties)); + } + + /** + * Call this method to report a change in provider packages. + */ + protected void setPackageNames(Set<String> packageNames) { + setState(state -> state.withProviderPackageNames(packageNames)); } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a new location. */ protected void reportLocation(Location location) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(location); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(location); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a new location. */ protected void reportLocation(List<Location> locations) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(locations); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(locations); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Invoked by the location service to return a list of packages currently associated with this - * provider. May be called from any thread. + * Sets a new request and worksource for the provider. */ - public List<String> getProviderPackages() { - return Collections.singletonList(mContext.getPackageName()); + public final void setRequest(ProviderRequest request) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onSetRequest(request)); } /** - * Invoked by the location service to deliver a new request for fulfillment to the provider. - * Replaces any previous requests completely. Will always be invoked from the location service - * thread with a cleared binder identity. + * Always invoked on the provider executor. */ - public abstract void onSetRequest(ProviderRequest request, WorkSource source); + protected abstract void onSetRequest(ProviderRequest request); + + /** + * Sends an extra command to the provider for it to interpret as it likes. + */ + public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras)); + } /** - * Invoked by the location service to deliver a custom command to this provider. Will always be - * invoked from the location service thread with a cleared binder identity. + * Always invoked on the provider executor. */ - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {} + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} /** - * Invoked by the location service to dump debug or log information. May be invoked from any - * thread. + * Dumps debug or log information. May be invoked from any thread. */ public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args); } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index d8561b697caa..15cf190952d1 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -43,6 +43,7 @@ import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; @@ -113,8 +114,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private static final ProviderProperties PROPERTIES = new ProviderProperties( - true, true, false, false, true, true, true, - Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); + /* requiresNetwork = */false, + /* requiresSatellite = */true, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportAltitude = */true, + /* supportsSpeed = */true, + /* supportsBearing = */true, + Criteria.POWER_HIGH, + Criteria.ACCURACY_FINE); // these need to match GnssPositionMode enum in IGnss.hal private static final int GPS_POSITION_MODE_STANDALONE = 0; @@ -616,13 +624,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } - public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, - Looper looper) { - super(context, locationProviderManager); + public GnssLocationProvider(Context context, Handler handler) { + super(context, new HandlerExecutor(handler)); ensureInitialized(); - mLooper = looper; + mLooper = handler.getLooper(); // Create a wake lock mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -639,7 +646,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context, - GnssLocationProvider.this::onNetworkAvailable, looper); + GnssLocationProvider.this::onNetworkAvailable, mLooper); // App ops service to keep track of who is accessing the GPS mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -649,7 +656,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements BatteryStats.SERVICE_NAME)); // Construct internal handler - mHandler = new ProviderHandler(looper); + mHandler = new ProviderHandler(mLooper); // Load GPS configuration and register listeners in the background: // some operations, such as opening files and registering broadcast receivers, can take a @@ -693,10 +700,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements }; mGnssMetrics = new GnssMetrics(mBatteryStats); - mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this); + mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this); GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper = new GnssSatelliteBlacklistHelper(mContext, - looper, this); + mLooper, this); mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist); mGnssBatchingProvider = new GnssBatchingProvider(); mGnssGeofenceProvider = new GnssGeofenceProvider(); @@ -1047,8 +1054,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); + public void onSetRequest(ProviderRequest request) { + sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource)); } private void handleSetRequest(ProviderRequest request, WorkSource source) { @@ -1185,7 +1192,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { long identity = Binder.clearCallingIdentity(); try { @@ -2064,10 +2071,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } /** - * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager, - * Looper)}. - * It is in charge of loading properties and registering for events that will be posted to - * this handler. + * This method is bound to the constructor. It is in charge of loading properties and + * registering for events that will be posted to this handler. */ private void handleInitialize() { // class_init_native() already initializes the GNSS service handle during class loading. diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index 694f14904668..8a149afa6238 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -23,12 +23,13 @@ import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; -import android.os.WorkSource; +import android.util.ArraySet; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ILocationProvider; import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; @@ -39,10 +40,8 @@ import com.android.server.ServiceWatcher; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; /** * Proxy for ILocationProvider implementations. @@ -52,59 +51,64 @@ public class LocationProviderProxy extends AbstractLocationProvider { private static final String TAG = "LocationProviderProxy"; private static final boolean D = LocationManagerService.D; - // used to ensure that updates to mProviderPackages are atomic - private final Object mProviderPackagesLock = new Object(); - - // used to ensure that updates to mRequest and mWorkSource are atomic - private final Object mRequestLock = new Object(); + private static final int MAX_ADDITIONAL_PACKAGES = 2; private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() { // executed on binder thread @Override public void onSetAdditionalProviderPackages(List<String> packageNames) { - LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames); + int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1; + ArraySet<String> allPackages = new ArraySet<>(maxCount); + allPackages.add(mServiceWatcher.getCurrentPackageName()); + for (String packageName : packageNames) { + if (packageNames.size() >= maxCount) { + return; + } + + try { + mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); + allPackages.add(packageName); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " + + packageName); + } + } + + setPackageNames(allPackages); } // executed on binder thread @Override public void onSetEnabled(boolean enabled) { - LocationProviderProxy.this.setEnabled(enabled); + setEnabled(enabled); } // executed on binder thread @Override public void onSetProperties(ProviderProperties properties) { - LocationProviderProxy.this.setProperties(properties); + setProperties(properties); } // executed on binder thread @Override public void onReportLocation(Location location) { - LocationProviderProxy.this.reportLocation(location); + reportLocation(location); } }; private final ServiceWatcher mServiceWatcher; - @GuardedBy("mProviderPackagesLock") - private final CopyOnWriteArrayList<String> mProviderPackages = new CopyOnWriteArrayList<>(); - - @GuardedBy("mRequestLock") - @Nullable - private ProviderRequest mRequest; - @GuardedBy("mRequestLock") - private WorkSource mWorkSource; + @Nullable private ProviderRequest mRequest; /** * Creates a new LocationProviderProxy and immediately begins binding to the best applicable * service. */ @Nullable - public static LocationProviderProxy createAndBind( - Context context, LocationProviderManager locationProviderManager, String action, + public static LocationProviderProxy createAndBind(Context context, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager, + LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(), action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId); if (proxy.bind()) { @@ -114,14 +118,13 @@ public class LocationProviderProxy extends AbstractLocationProvider { } } - private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager, - String action, int overlaySwitchResId, int defaultServicePackageNameResId, + private LocationProviderProxy(Context context, Handler handler, String action, + int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - super(context, locationProviderManager); + super(context, new HandlerExecutor(handler), Collections.emptySet()); mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, - FgThread.getHandler()) { + defaultServicePackageNameResId, initialPackageNamesResId, handler) { @Override protected void onBind() { @@ -130,14 +133,11 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override protected void onUnbind() { - resetProviderPackages(Collections.emptyList()); - setEnabled(false); - setProperties(null); + setState(State.EMPTY_STATE); } }; mRequest = null; - mWorkSource = new WorkSource(); } private boolean bind() { @@ -148,77 +148,34 @@ public class LocationProviderProxy extends AbstractLocationProvider { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); - resetProviderPackages(Collections.emptyList()); + setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName())); service.setLocationProviderManager(mManager); - synchronized (mRequestLock) { - if (mRequest != null) { - service.setRequest(mRequest, mWorkSource); - } - } - } - - @Override - public List<String> getProviderPackages() { - synchronized (mProviderPackagesLock) { - return mProviderPackages; + if (mRequest != null) { + service.setRequest(mRequest, mRequest.workSource); } } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - synchronized (mRequestLock) { - mRequest = request; - mWorkSource = source; - } + public void onSetRequest(ProviderRequest request) { mServiceWatcher.runOnBinder(binder -> { + mRequest = request; ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - service.setRequest(request, source); + service.setRequest(request, request.workSource); }); } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("service=" + mServiceWatcher); - synchronized (mProviderPackagesLock) { - if (mProviderPackages.size() > 1) { - pw.println("additional packages=" + mProviderPackages); - } - } - } - - @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); service.sendExtraCommand(command, extras); }); } - private void onSetAdditionalProviderPackages(List<String> packageNames) { - resetProviderPackages(packageNames); - } - - private void resetProviderPackages(List<String> additionalPackageNames) { - ArrayList<String> permittedPackages = new ArrayList<>(additionalPackageNames.size()); - for (String packageName : additionalPackageNames) { - try { - mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); - permittedPackages.add(packageName); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " - + packageName); - } - } - - synchronized (mProviderPackagesLock) { - mProviderPackages.clear(); - String myPackage = mServiceWatcher.getCurrentPackageName(); - if (myPackage != null) { - mProviderPackages.add(myPackage); - mProviderPackages.addAll(permittedPackages); - } - } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("service=" + mServiceWatcher); } } diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java index f625452975c0..0e8720ebb08f 100644 --- a/services/core/java/com/android/server/location/LocationSettingsStore.java +++ b/services/core/java/com/android/server/location/LocationSettingsStore.java @@ -16,6 +16,8 @@ package com.android.server.location; +import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS; @@ -28,6 +30,7 @@ import android.app.ActivityManager; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; +import android.os.Binder; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -248,6 +251,9 @@ public class LocationSettingsStore { DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS); } + /** + * Retrieve maximum age of the last location. + */ public long getMaxLastLocationAgeMs() { return Settings.Global.getLong( mContext.getContentResolver(), @@ -256,6 +262,29 @@ public class LocationSettingsStore { } /** + * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for + * backwards compatibility for old clients, and may be removed in the future. + */ + public void setLocationProviderAllowed(String provider, boolean enabled, int userId) { + // fused and passive provider never get public updates for legacy reasons + if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) { + return; + } + + long identity = Binder.clearCallingIdentity(); + try { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + (enabled ? "+" : "-") + provider, + userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Dump info for debugging. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java index 472876bfd86a..60c9fc12c201 100644 --- a/services/core/java/com/android/server/location/MockProvider.java +++ b/services/core/java/com/android/server/location/MockProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.annotation.Nullable; import android.content.Context; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -34,41 +33,33 @@ import java.io.PrintWriter; */ public class MockProvider extends AbstractLocationProvider { - private boolean mEnabled; @Nullable private Location mLocation; - public MockProvider(Context context, - LocationProviderManager locationProviderManager, ProviderProperties properties) { - super(context, locationProviderManager); - - mEnabled = true; - mLocation = null; - + public MockProvider(Context context, ProviderProperties properties) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); setProperties(properties); } /** Sets the enabled state of this mock provider. */ - public void setEnabled(boolean enabled) { - mEnabled = enabled; - super.setEnabled(enabled); + public void setProviderEnabled(boolean enabled) { + setEnabled(enabled); } /** Sets the location to report for this mock provider. */ - public void setLocation(Location l) { - mLocation = new Location(l); - if (!mLocation.isFromMockProvider()) { - mLocation.setIsFromMockProvider(true); - } - if (mEnabled) { - reportLocation(mLocation); - } + public void setProviderLocation(Location l) { + Location location = new Location(l); + location.setIsFromMockProvider(true); + mLocation = location; + reportLocation(location); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("last location=" + mLocation); + pw.println("last mock location=" + mLocation); } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) {} + public void onSetRequest(ProviderRequest request) {} } diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java new file mode 100644 index 000000000000..f50dfe7edbb7 --- /dev/null +++ b/services/core/java/com/android/server/location/MockableLocationProvider.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 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.server.location; + +import android.annotation.Nullable; +import android.content.Context; +import android.location.Location; +import android.os.Bundle; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.location.ProviderRequest; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; + +/** + * Represents a location provider that may switch between a mock implementation and a real + * implementation. Requires owners to provide a lock object that will be used internally and held + * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead + * to deadlock. + * + * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can + * be held BOTH when calling into this class AND when receiving a callback from this class is the + * lock given to this class via the constructor. Holding any other lock is ok as long as there is no + * possibility that it can be obtained within both codepaths. + * + * Holding the given lock guarantees atomicity of any operations on this class for the duration. + * + * @hide + */ +public class MockableLocationProvider extends AbstractLocationProvider { + + private final Object mOwnerLock; + + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mProvider; + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mRealProvider; + @GuardedBy("mOwnerLock") + @Nullable private MockProvider mMockProvider; + + @GuardedBy("mOwnerLock") + private ProviderRequest mRequest; + + /** + * The given lock object will be held any time the listener is invoked, and may also be acquired + * and released during the course of invoking any public methods. Holding the given lock ensures + * that provider state cannot change except as result of an explicit call by the owner of the + * lock into this class. The client is reponsible for ensuring this cannot cause deadlock. + * + * The client should expect that it may being to receive callbacks as soon as this constructor + * is invoked. + */ + public MockableLocationProvider(Context context, Object ownerLock, Listener listener) { + // using a direct executor is acceptable because all inbound calls are delegated to the + // actual provider implementations which will use their own executors + super(context, Runnable::run, Collections.emptySet()); + mOwnerLock = ownerLock; + mRequest = ProviderRequest.EMPTY_REQUEST; + + setListener(listener); + } + + /** + * Returns the current provider implementation. May be null if there is no current + * implementation. + */ + @Nullable + public AbstractLocationProvider getProvider() { + synchronized (mOwnerLock) { + return mProvider; + } + } + + /** + * Sets the real provider implementation, replacing any previous real provider implementation. + * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this + * results in a state change. + */ + public void setRealProvider(@Nullable AbstractLocationProvider provider) { + synchronized (mOwnerLock) { + if (mRealProvider == provider) { + return; + } + + mRealProvider = provider; + if (!isMock()) { + setProviderLocked(mRealProvider); + } + } + } + + /** + * Sets the mock provider implementation, replacing any previous mock provider implementation. + * Mock implementations are always used instead of real implementations if set. May cause an + * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a + * state change. + */ + public void setMockProvider(@Nullable MockProvider provider) { + synchronized (mOwnerLock) { + if (mMockProvider == provider) { + return; + } + + mMockProvider = provider; + if (mMockProvider != null) { + setProviderLocked(mMockProvider); + } else { + setProviderLocked(mRealProvider); + } + } + } + + @GuardedBy("mOwnerLock") + private void setProviderLocked(@Nullable AbstractLocationProvider provider) { + if (mProvider == provider) { + return; + } + + AbstractLocationProvider oldProvider = mProvider; + mProvider = provider; + + if (oldProvider != null) { + // do this after switching the provider - so even if the old provider is using a direct + // executor, if it re-enters this class within setRequest(), it will be ignored + oldProvider.setListener(null); + oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST); + } + + State newState; + if (mProvider != null) { + newState = mProvider.setListener(new ListenerWrapper(mProvider)); + } else { + newState = State.EMPTY_STATE; + } + + ProviderRequest oldRequest = mRequest; + setState(newState); + + if (mProvider != null && oldRequest == mRequest) { + mProvider.setRequest(mRequest); + } + } + + /** + * Returns true if the current active provider implementation is the mock implementation, and + * false otherwise. + */ + public boolean isMock() { + synchronized (mOwnerLock) { + return mMockProvider != null && mProvider == mMockProvider; + } + } + + /** + * Sets the mock provider implementation's enabled state. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderEnabled(boolean enabled) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderEnabled(enabled); + } + } + /** + * Sets the mock provider implementation's location. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderLocation(Location location) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderLocation(location); + } + } + + @Override + public State getState() { + return super.getState(); + } + + /** + * Returns the current location request. + */ + public ProviderRequest getCurrentRequest() { + synchronized (mOwnerLock) { + return mRequest; + } + } + + protected void onSetRequest(ProviderRequest request) { + synchronized (mOwnerLock) { + if (request == mRequest) { + return; + } + + mRequest = request; + + if (mProvider != null) { + mProvider.setRequest(request); + } + } + } + + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) { + synchronized (mOwnerLock) { + if (mProvider != null) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + } + } + + /** + * Dumps the current provider implementation. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + AbstractLocationProvider provider; + synchronized (mOwnerLock) { + provider = mProvider; + pw.println("request=" + mRequest); + } + + if (provider != null) { + // dump outside the lock in case the provider wants to acquire its own locks, and since + // the default provider dump behavior does not move things onto the provider thread... + provider.dump(fd, pw, args); + } + } + + // ensures that callbacks from the incorrect provider are never visible to clients - this + // requires holding the owner's lock for the duration of the callback + private class ListenerWrapper implements Listener { + + private final AbstractLocationProvider mListenerProvider; + + private ListenerWrapper(AbstractLocationProvider listenerProvider) { + mListenerProvider = listenerProvider; + } + + @Override + public final void onStateChanged(State oldState, State newState) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + setState(newState); + } + } + + @Override + public final void onReportLocation(Location location) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(location); + } + } + + @Override + public final void onReportLocation(List<Location> locations) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(locations); + } + } + } +} diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index 639b1eb1ed5e..b33877069d70 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.content.Context; import android.location.Criteria; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -37,13 +36,22 @@ import java.io.PrintWriter; public class PassiveProvider extends AbstractLocationProvider { private static final ProviderProperties PROPERTIES = new ProviderProperties( - false, false, false, false, false, false, false, - Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); + /* requiresNetwork = */false, + /* requiresSatellite = */false, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportsAltitude = */false, + /* supportsSpeed = */false, + /* supportsBearing = */false, + Criteria.POWER_LOW, + Criteria.ACCURACY_COARSE); - private boolean mReportLocation; + private volatile boolean mReportLocation; - public PassiveProvider(Context context, LocationProviderManager locationProviderManager) { - super(context, locationProviderManager); + public PassiveProvider(Context context) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); mReportLocation = false; @@ -52,7 +60,7 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { + public void onSetRequest(ProviderRequest request) { mReportLocation = request.reportLocation; } @@ -63,7 +71,5 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("report location=" + mReportLocation); - } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {} } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index d2e54f9cd64c..46ea9d11d1dc 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -25,11 +25,13 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserManager; import android.util.Slog; +import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.RebootEscrowListener; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicBoolean; @@ -109,20 +111,50 @@ class RebootEscrowManager { } void loadRebootEscrowDataIfAvailable() { + List<UserInfo> users = mUserManager.getUsers(); + List<UserInfo> rebootEscrowUsers = new ArrayList<>(); + for (UserInfo user : users) { + if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { + rebootEscrowUsers.add(user); + } + } + + if (rebootEscrowUsers.isEmpty()) { + return; + } + + SecretKeySpec escrowKey = getAndClearRebootEscrowKey(); + if (escrowKey == null) { + Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); + for (UserInfo user : users) { + mStorage.removeRebootEscrow(user.id); + } + StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false); + return; + } + + boolean allUsersUnlocked = true; + for (UserInfo user : rebootEscrowUsers) { + allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey); + } + StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked); + } + + private SecretKeySpec getAndClearRebootEscrowKey() { IRebootEscrow rebootEscrow = mInjector.getRebootEscrow(); if (rebootEscrow == null) { - return; + return null; } - final SecretKeySpec escrowKey; try { byte[] escrowKeyBytes = rebootEscrow.retrieveKey(); if (escrowKeyBytes == null) { - return; + Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key"); + return null; } else if (escrowKeyBytes.length != 32) { Slog.e(TAG, "IRebootEscrow returned key of incorrect size " + escrowKeyBytes.length); - return; + return null; } // Make sure we didn't get the null key. @@ -132,29 +164,22 @@ class RebootEscrowManager { } if (zero == 0) { Slog.w(TAG, "IRebootEscrow returned an all-zeroes key"); - return; + return null; } // Overwrite the existing key with the null key rebootEscrow.storeKey(new byte[32]); - escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes); + return RebootEscrowData.fromKeyBytes(escrowKeyBytes); } catch (RemoteException e) { Slog.w(TAG, "Could not retrieve escrow data"); - return; - } - - List<UserInfo> users = mUserManager.getUsers(); - for (UserInfo user : users) { - if (mCallbacks.isUserSecure(user.id)) { - restoreRebootEscrowForUser(user.id, escrowKey); - } + return null; } } - private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) { + private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) { if (!mStorage.hasRebootEscrow(userId)) { - return; + return false; } try { @@ -165,9 +190,11 @@ class RebootEscrowManager { mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(), escrowData.getSyntheticPassword(), userId); + return true; } catch (IOException e) { Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e); } + return false; } void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion, diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 29338ba06dc2..52750f392d5b 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L import android.content.Context; import android.os.RemoteException; +import android.os.UserHandle; import android.security.Scrypt; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; @@ -163,16 +164,28 @@ public class KeySyncTask implements Runnable { } private void syncKeys() throws RemoteException { + int generation = mPlatformKeyManager.getGenerationId(mUserId); if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // Application keys for the user will not be available for sync. Log.w(TAG, "Credentials are not set for user " + mUserId); - int generation = mPlatformKeyManager.getGenerationId(mUserId); - mPlatformKeyManager.invalidatePlatformKey(mUserId, generation); + if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED + || mUserId != UserHandle.USER_SYSTEM) { + // Only invalidate keys with legacy protection param. + mPlatformKeyManager.invalidatePlatformKey(mUserId, generation); + } return; } if (isCustomLockScreen()) { - Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId); - mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId); + Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId); + // Keys will be synced when user starts using non custom screen lock. + if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED + || mUserId != UserHandle.USER_SYSTEM) { + mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId); + } + return; + } + if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) { + Log.w(TAG, "Can't sync keys for locked user " + mUserId); return; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java index 0ad6c2a69556..0761cde825b6 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -67,8 +67,9 @@ import javax.crypto.spec.GCMParameterSpec; * @hide */ public class PlatformKeyManager { - private static final String TAG = "PlatformKeyManager"; + static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000; + private static final String TAG = "PlatformKeyManager"; private static final String KEY_ALGORITHM = "AES"; private static final int KEY_SIZE_BITS = 256; private static final String KEY_ALIAS_PREFIX = @@ -131,14 +132,14 @@ public class PlatformKeyManager { /** * Returns {@code true} if the platform key is available. A platform key won't be available if - * the user has not set up a lock screen. + * device is locked. * * @param userId The ID of the user to whose lock screen the platform key must be bound. * * @hide */ - public boolean isAvailable(int userId) { - return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId); + public boolean isDeviceLocked(int userId) { + return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId); } /** @@ -169,7 +170,6 @@ public class PlatformKeyManager { * @param userId The ID of the user to whose lock screen the platform key must be bound. * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. * @throws KeyStoreException if there is an error in AndroidKeyStore. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -177,13 +177,8 @@ public class PlatformKeyManager { */ @VisibleForTesting void regenerate(int userId) - throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException, + throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException { - if (!isAvailable(userId)) { - throw new InsecureUserException(String.format( - Locale.US, "%d does not have a lock screen set.", userId)); - } - int generationId = getGenerationId(userId); int nextId; if (generationId == -1) { @@ -192,6 +187,7 @@ public class PlatformKeyManager { invalidatePlatformKey(userId, generationId); nextId = generationId + 1; } + generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED); generateAndLoadKey(userId, nextId); } @@ -203,7 +199,6 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -211,7 +206,7 @@ public class PlatformKeyManager { */ public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, - InsecureUserException, IOException, RemoteException { + IOException, RemoteException { init(userId); try { // Try to see if the decryption key is still accessible before using the encryption key. @@ -234,12 +229,11 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * * @hide */ private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException, - UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { + UnrecoverableKeyException, NoSuchAlgorithmException { int generationId = getGenerationId(userId); String alias = getEncryptAlias(userId, generationId); if (!isKeyLoaded(userId, generationId)) { @@ -258,7 +252,6 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -266,7 +259,7 @@ public class PlatformKeyManager { */ public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, - InsecureUserException, IOException, RemoteException { + IOException, RemoteException { init(userId); try { PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId); @@ -288,12 +281,11 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * * @hide */ private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException, - UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { + UnrecoverableKeyException, NoSuchAlgorithmException { int generationId = getGenerationId(userId); String alias = getDecryptAlias(userId, generationId); if (!isKeyLoaded(userId, generationId)) { @@ -340,13 +332,8 @@ public class PlatformKeyManager { * @hide */ void init(int userId) - throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException, + throws KeyStoreException, NoSuchAlgorithmException, IOException, RemoteException { - if (!isAvailable(userId)) { - throw new InsecureUserException(String.format( - Locale.US, "%d does not have a lock screen set.", userId)); - } - int generationId = getGenerationId(userId); if (isKeyLoaded(userId, generationId)) { Log.i(TAG, String.format( @@ -363,6 +350,7 @@ public class PlatformKeyManager { generationId++; } + generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED); generateAndLoadKey(userId, generationId); } @@ -440,12 +428,16 @@ public class PlatformKeyManager { KeyProtection.Builder decryptionKeyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) - .setUserAuthenticationRequired(true) - .setUserAuthenticationValidityDurationSeconds( - USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE); - if (userId != UserHandle.USER_SYSTEM) { + // Skip UserAuthenticationRequired for main user + if (userId == UserHandle.USER_SYSTEM) { + decryptionKeyProtection.setUnlockedDeviceRequired(true); + } else { + // With setUnlockedDeviceRequired, KeyStore thinks that device is locked . + decryptionKeyProtection.setUserAuthenticationRequired(true); + decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds( + USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS); // Bind decryption key to secondary profile lock screen secret. long secureUserId = getGateKeeperService().getSecureUserId(userId); // TODO(b/124095438): Propagate this failure instead of silently failing. diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 383d5cf326c0..6d97ed7a69a7 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -19,7 +19,6 @@ package com.android.server.locksettings.recoverablekeystore; import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED; import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE; -import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING; @@ -46,7 +45,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; -import com.android.internal.util.Preconditions; import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils; import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException; @@ -76,8 +74,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.crypto.AEADBadTagException; @@ -89,13 +88,14 @@ import javax.crypto.AEADBadTagException; */ public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreMgr"; + private static final long SYNC_DELAY_MILLIS = 2000; private static RecoverableKeyStoreManager mInstance; private final Context mContext; private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; - private final ExecutorService mExecutorService; + private final ScheduledExecutorService mExecutorService; private final RecoverySnapshotListenersStorage mListenersStorage; private final RecoverableKeyGenerator mRecoverableKeyGenerator; private final RecoverySnapshotStorage mSnapshotStorage; @@ -136,7 +136,7 @@ public class RecoverableKeyStoreManager { context.getApplicationContext(), db, new RecoverySessionStorage(), - Executors.newSingleThreadExecutor(), + Executors.newScheduledThreadPool(1), snapshotStorage, new RecoverySnapshotListenersStorage(), platformKeyManager, @@ -152,7 +152,7 @@ public class RecoverableKeyStoreManager { Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, - ExecutorService executorService, + ScheduledExecutorService executorService, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, @@ -724,8 +724,6 @@ public class RecoverableKeyStoreManager { throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } catch (InsecureUserException e) { - throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { @@ -793,8 +791,6 @@ public class RecoverableKeyStoreManager { throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } catch (InsecureUserException e) { - throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { @@ -915,7 +911,7 @@ public class RecoverableKeyStoreManager { int storedHashType, @NonNull byte[] credential, int userId) { // So as not to block the critical path unlocking the phone, defer to another thread. try { - mExecutorService.execute(KeySyncTask.newInstance( + mExecutorService.schedule(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, @@ -923,7 +919,10 @@ public class RecoverableKeyStoreManager { userId, storedHashType, credential, - /*credentialUpdated=*/ false)); + /*credentialUpdated=*/ false), + SYNC_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { @@ -947,7 +946,7 @@ public class RecoverableKeyStoreManager { int userId) { // So as not to block the critical path unlocking the phone, defer to another thread. try { - mExecutorService.execute(KeySyncTask.newInstance( + mExecutorService.schedule(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, @@ -955,7 +954,10 @@ public class RecoverableKeyStoreManager { userId, storedHashType, credential, - /*credentialUpdated=*/ true)); + /*credentialUpdated=*/ true), + SYNC_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 9ca302edd204..9c9a4121830f 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -47,11 +47,11 @@ abstract class MediaRoute2Provider { public abstract void requestCreateSession(String packageName, String routeId, String routeType, long requestId); - public abstract void releaseSession(int sessionId); + public abstract void releaseSession(String sessionId); - public abstract void selectRoute(int sessionId, String routeId); - public abstract void deselectRoute(int sessionId, String routeId); - public abstract void transferToRoute(int sessionId, String routeId); + public abstract void selectRoute(String sessionId, String routeId); + public abstract void deselectRoute(String sessionId, String routeId); + public abstract void transferToRoute(String sessionId, String routeId); public abstract void sendControlRequest(String routeId, Intent request); public abstract void requestSetVolume(String routeId, int volume); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 5cc2b16e073a..635983575226 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -86,7 +86,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { if (mConnectionReady) { mActiveConnection.releaseSession(sessionId); updateBinding(); @@ -94,21 +94,21 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(String sessionId, String routeId) { if (mConnectionReady) { mActiveConnection.selectRoute(sessionId, routeId); } } @Override - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(String sessionId, String routeId) { if (mConnectionReady) { mActiveConnection.deselectRoute(sessionId, routeId); } } @Override - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(String sessionId, String routeId) { if (mConnectionReady) { mActiveConnection.transferToRoute(sessionId, routeId); } @@ -302,6 +302,11 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv + mComponentName); return; } + + sessionInfo = new RouteSessionInfo.Builder(sessionInfo) + .setProviderId(getUniqueId()) + .build(); + mCallback.onSessionInfoChanged(this, sessionInfo); } @@ -355,7 +360,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { try { mProvider.releaseSession(sessionId); } catch (RemoteException ex) { @@ -363,7 +368,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(String sessionId, String routeId) { try { mProvider.selectRoute(sessionId, routeId); } catch (RemoteException ex) { @@ -371,7 +376,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(String sessionId, String routeId) { try { mProvider.deselectRoute(sessionId, routeId); } catch (RemoteException ex) { @@ -379,7 +384,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(String sessionId, String routeId) { try { mProvider.transferToRoute(sessionId, routeId); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index b48243279d9e..487ab5201278 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -16,6 +16,9 @@ package com.android.server.media; +import static android.media.MediaRouter2Utils.getOriginalId; +import static android.media.MediaRouter2Utils.getProviderId; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; @@ -197,6 +200,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -213,6 +219,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -228,6 +237,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -241,6 +253,9 @@ class MediaRouter2ServiceImpl { public void releaseSession(IMediaRouter2Client client, String uniqueSessionId) { Objects.requireNonNull(client, "client must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -607,7 +622,7 @@ class MediaRouter2ServiceImpl { } long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId); if (clientRecord != null && managerRecord.mTrusted) { - //TODO: select category properly + //TODO: select route type properly requestCreateSessionLocked(clientRecord.mClient, route, route.getRouteTypes().get(0), uniqueRequestId); } @@ -1002,8 +1017,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), - route.getOriginalId()); + provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } private void deselectRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1019,8 +1033,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), - route.getOriginalId()); + provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } private void transferToRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1036,7 +1049,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId), + provider.transferToRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } @@ -1068,9 +1081,9 @@ class MediaRouter2ServiceImpl { return false; } - final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId); + final String sessionId = getOriginalId(uniqueSessionId); if (sessionId == null) { - Slog.w(TAG, "Failed to get int session id from unique session id. " + Slog.w(TAG, "Failed to get original session id from unique session id. " + "uniqueSessionId=" + uniqueSessionId); return false; } @@ -1093,14 +1106,14 @@ class MediaRouter2ServiceImpl { return; } - final String providerId = RouteSessionInfo.getProviderId(uniqueSessionId); + final String providerId = getProviderId(uniqueSessionId); if (providerId == null) { Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. " + "uniqueSessionId=" + uniqueSessionId); return; } - final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId); + final String sessionId = getOriginalId(uniqueSessionId); if (sessionId == null) { Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. " + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId); @@ -1146,15 +1159,15 @@ class MediaRouter2ServiceImpl { } String originalRouteId = matchingRequest.mRoute.getId(); - String originalCategory = matchingRequest.mRouteType; + String originalRouteType = matchingRequest.mRouteType; Client2Record client2Record = matchingRequest.mClientRecord; if (!sessionInfo.getSelectedRoutes().contains(originalRouteId) - || !TextUtils.equals(originalCategory, + || !TextUtils.equals(originalRouteType, sessionInfo.getRouteType())) { Slog.w(TAG, "Created session doesn't match the original request." + " originalRouteId=" + originalRouteId - + ", originalCategory=" + originalCategory + ", requestId=" + requestId + + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo); notifySessionCreationFailed(matchingRequest.mClientRecord, toClientRequestId(requestId)); @@ -1164,41 +1177,34 @@ class MediaRouter2ServiceImpl { // Succeeded notifySessionCreated(matchingRequest.mClientRecord, sessionInfo, toClientRequestId(requestId)); - mSessionToClientMap.put(sessionInfo.getUniqueSessionId(), client2Record); + mSessionToClientMap.put(sessionInfo.getId(), client2Record); // TODO: Tell managers for the session creation } private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RouteSessionInfo sessionInfo) { - RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(provider.getUniqueId()) - .build(); Client2Record client2Record = mSessionToClientMap.get( - sessionInfoWithProviderId.getUniqueSessionId()); + sessionInfo.getId()); if (client2Record == null) { - Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId); + Slog.w(TAG, "No matching client found for session=" + sessionInfo); // TODO: Tell managers for the session update return; } - notifySessionInfoChanged(client2Record, sessionInfoWithProviderId); + notifySessionInfoChanged(client2Record, sessionInfo); // TODO: Tell managers for the session update } private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RouteSessionInfo sessionInfo) { - RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(provider.getUniqueId()) - .build(); - Client2Record client2Record = mSessionToClientMap.get( - sessionInfoWithProviderId.getUniqueSessionId()); + Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId()); if (client2Record == null) { - Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId); + Slog.w(TAG, "No matching client found for session=" + sessionInfo); // TODO: Tell managers for the session release return; } - notifySessionReleased(client2Record, sessionInfoWithProviderId); + notifySessionReleased(client2Record, sessionInfo); // TODO: Tell managers for the session release } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index daf603012391..0ea4e63231d4 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -48,8 +48,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE"; // TODO: Move these to a proper place - public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO"; - public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO"; + public static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; + public static final String TYPE_LIVE_VIDEO = "android.media.intent.route.TYPE_LIVE_VIDEO"; private final AudioManager mAudioManager; private final IAudioService mAudioService; @@ -97,22 +97,22 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { // Do nothing } @Override - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(String sessionId, String routeId) { //TODO: implement method } @Override - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(String sessionId, String routeId) { //TODO: implement method } @Override - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(String sessionId, String routeId) { //TODO: implement method } @@ -141,8 +141,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(CATEGORY_LIVE_AUDIO) - .addRouteType(CATEGORY_LIVE_VIDEO) + .addRouteType(TYPE_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_VIDEO) .build(); AudioRoutesInfo newAudioRoutes = null; @@ -181,8 +181,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addRouteType(CATEGORY_LIVE_AUDIO) - .addRouteType(CATEGORY_LIVE_VIDEO) + .addRouteType(TYPE_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_VIDEO) .build(); if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { @@ -193,7 +193,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mCurAudioRoutesInfo.bluetoothName) .setDescription(mContext.getResources().getText( R.string.bluetooth_a2dp_audio_route_name).toString()) - .addRouteType(CATEGORY_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_AUDIO) .build(); } else { mBluetoothA2dpRoute = null; diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index d66fd574ef4a..da2ca9b0cfde 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -43,7 +43,8 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor } record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(), - record.sbn.getUid(), record.getChannel().getId(), false)); + record.sbn.getUid(), record.getChannel().getId(), + record.getNotification().getShortcutId(), false)); return null; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a4f4d079df27..0e08033e0f68 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -25,6 +25,7 @@ import static android.app.Notification.FLAG_INSISTENT; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; @@ -236,6 +237,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; import com.android.server.DeviceIdleInternal; @@ -1008,12 +1010,14 @@ public class NotificationManagerService extends SystemService { if (clearEffects) { clearEffects(); } + mAssistants.onPanelRevealed(items); } @Override public void onPanelHidden() { MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL); EventLogTags.writeNotificationPanelHidden(); + mAssistants.onPanelHidden(); } @Override @@ -1060,6 +1064,7 @@ public class NotificationManagerService extends SystemService { reportSeen(r); } r.setVisibility(true, nv.rank, nv.count); + mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true); boolean isHun = (nv.location == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); // hasBeenVisiblyExpanded must be called after updating the expansion state of @@ -1078,6 +1083,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(nv.key); if (r == null) continue; r.setVisibility(false, nv.rank, nv.count); + mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false); nv.recycle(); } } @@ -2219,8 +2225,8 @@ public class NotificationManagerService extends SystemService { maybeNotifyChannelOwner(pkg, uid, preUpdate, channel); if (!fromListener) { - final NotificationChannel modifiedChannel = - mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false); + final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel( + pkg, uid, channel.getId(), false); mListeners.notifyNotificationChannelChanged( pkg, UserHandle.getUserHandleForUid(uid), modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); @@ -2481,7 +2487,7 @@ public class NotificationManagerService extends SystemService { .setUid(r.sbn.getUid()) .setChannelId(r.getChannel().getId()) .setChannelName(r.getChannel().getName().toString()) - .setPostedTimeMs(r.sbn.getPostTime()) + .setPostedTimeMs(System.currentTimeMillis()) .setTitle(getHistoryTitle(r.getNotification())) .setText(getHistoryText( r.sbn.getPackageContext(getContext()), r.getNotification())) @@ -3017,21 +3023,43 @@ public class NotificationManagerService extends SystemService { @Override public void createNotificationChannels(String pkg, - ParceledListSlice channelsList) throws RemoteException { + ParceledListSlice channelsList) { checkCallerIsSystemOrSameApp(pkg); createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList); } @Override public void createNotificationChannelsForPackage(String pkg, int uid, - ParceledListSlice channelsList) throws RemoteException { - checkCallerIsSystem(); + ParceledListSlice channelsList) { + enforceSystemOrSystemUI("only system can call this"); createNotificationChannelsImpl(pkg, uid, channelsList); } @Override + public void createConversationNotificationChannelForPackage(String pkg, int uid, + NotificationChannel parentChannel, String conversationId) { + enforceSystemOrSystemUI("only system can call this"); + Preconditions.checkNotNull(parentChannel); + Preconditions.checkNotNull(conversationId); + String parentId = parentChannel.getId(); + NotificationChannel conversationChannel = parentChannel; + conversationChannel.setId(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId)); + conversationChannel.setConversationId(parentId, conversationId); + createNotificationChannelsImpl( + pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel))); + } + + @Override public NotificationChannel getNotificationChannel(String callingPkg, int userId, String targetPkg, String channelId) { + return getConversationNotificationChannel( + callingPkg, userId, targetPkg, channelId, null); + } + + @Override + public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, + String targetPkg, String channelId, String conversationId) { if (canNotifyAsPackage(callingPkg, targetPkg, userId) || isCallingUidSystem()) { int targetUid = -1; @@ -3041,7 +3069,8 @@ public class NotificationManagerService extends SystemService { /* ignore */ } return mPreferencesHelper.getNotificationChannel( - targetPkg, targetUid, channelId, false /* includeDeleted */); + targetPkg, targetUid, channelId, conversationId, + false /* includeDeleted */); } throw new SecurityException("Pkg " + callingPkg + " cannot read channels for " + targetPkg + " in " + userId); @@ -3072,6 +3101,30 @@ public class NotificationManagerService extends SystemService { } @Override + public void deleteConversationNotificationChannels(String pkg, int uid, + String conversationId) { + checkCallerIsSystem(); + final int callingUid = Binder.getCallingUid(); + List<NotificationChannel> channels = + mPreferencesHelper.getNotificationChannelsByConversationId( + pkg, uid, conversationId); + if (!channels.isEmpty()) { + for (NotificationChannel nc : channels) { + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true, + UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null); + mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId()); + mListeners.notifyNotificationChannelChanged(pkg, + UserHandle.getUserHandleForUid(callingUid), + mPreferencesHelper.getNotificationChannel( + pkg, callingUid, nc.getId(), true), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + } + handleSavePolicyFile(); + } + } + + + @Override public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getNotificationChannelGroupWithChannels( @@ -5203,7 +5256,8 @@ public class NotificationManagerService extends SystemService { channelId = (new Notification.TvExtender(notification)).getChannelId(); } final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg, - notificationUid, channelId, false /* includeDeleted */); + notificationUid, channelId, notification.getShortcutId(), + false /* includeDeleted */); if (channel == null) { final String noChannelStr = "No Channel found for " + "pkg=" + pkg @@ -8296,6 +8350,32 @@ public class NotificationManagerService extends SystemService { } } + protected void onPanelRevealed(int items) { + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onPanelRevealed(items); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex); + } + }); + } + } + + protected void onPanelHidden() { + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onPanelHidden(); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex); + } + }); + } + } + boolean hasUserSet(int userId) { synchronized (mLock) { return mUserSetMap.getOrDefault(userId, false); @@ -8363,6 +8443,24 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") + void notifyAssistantVisibilityChangedLocked( + final StatusBarNotification sbn, + final boolean isVisible) { + final String key = sbn.getKey(); + Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key); + notifyAssistantLocked( + sbn, + false /* sameUserOnly */, + (assistant, sbnHolder) -> { + try { + assistant.onNotificationVisibilityChanged(key, isVisible); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex); + } + }); + } + + @GuardedBy("mNotificationLock") void notifyAssistantExpansionChangedLocked( final StatusBarNotification sbn, final boolean isUserAction, diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS new file mode 100644 index 000000000000..5a19656b36a6 --- /dev/null +++ b/services/core/java/com/android/server/notification/OWNERS @@ -0,0 +1,4 @@ +dsandler@android.com +juliacr@google.com +beverlyt@google.com +pixel@google.com diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index cdb0a17ada0a..92fcb7f99f9c 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -770,6 +770,13 @@ public class PreferencesHelper implements RankingConfig { channel.setShowBadge(false); } channel.setOriginalImportance(channel.getImportance()); + + // validate parent + if (channel.getParentChannelId() != null) { + Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()), + "Tried to create a conversation channel without a preexisting parent"); + } + r.channels.put(channel.getId(), channel); if (channel.canBypassDnd() != mAreChannelsBypassingDnd) { updateChannelsBypassingDnd(mContext.getUserId()); @@ -851,6 +858,13 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted) { Objects.requireNonNull(pkg); + return getNotificationChannel(pkg, uid, channelId, null, includeDeleted); + } + + @Override + public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + String conversationId, boolean includeDeleted) { + Preconditions.checkNotNull(pkg); synchronized (mPackagePreferences) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { @@ -859,14 +873,51 @@ public class PreferencesHelper implements RankingConfig { if (channelId == null) { channelId = NotificationChannel.DEFAULT_CHANNEL_ID; } - final NotificationChannel nc = r.channels.get(channelId); - if (nc != null && (includeDeleted || !nc.isDeleted())) { - return nc; + if (conversationId == null) { + final NotificationChannel nc = r.channels.get(channelId); + if (nc != null && (includeDeleted || !nc.isDeleted())) { + return nc; + } + } else { + // look for an automatically created conversation specific channel + return findConversationChannel(r, channelId, conversationId, includeDeleted); } return null; } } + private NotificationChannel findConversationChannel(PackagePreferences p, String parentId, + String conversationId, boolean includeDeleted) { + for (NotificationChannel nc : p.channels.values()) { + if (conversationId.equals(nc.getConversationId()) + && parentId.equals(nc.getParentChannelId()) + && (includeDeleted || !nc.isDeleted())) { + return nc; + } + } + return null; + } + + public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid, + String conversationId) { + Preconditions.checkNotNull(pkg); + Preconditions.checkNotNull(conversationId); + List<NotificationChannel> channels = new ArrayList<>(); + synchronized (mPackagePreferences) { + PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); + if (r == null) { + return channels; + } + for (NotificationChannel nc : r.channels.values()) { + if (conversationId.equals(nc.getConversationId()) + && !nc.isDeleted()) { + channels.add(nc); + } + } + return channels; + } + } + @Override public void deleteNotificationChannel(String pkg, int uid, String channelId) { synchronized (mPackagePreferences) { diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 7816f3619023..4b044c13ecc6 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -41,10 +41,15 @@ public interface RankingConfig { int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty); boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess); - void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); - NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted); + void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, + boolean fromUser); + NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + boolean includeDeleted); + NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + String conversationId, boolean includeDeleted); void deleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannels(String pkg, int uid); - ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted); + ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, + boolean includeDeleted); } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 307a07bb09a2..a009183f7ecb 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -32,10 +32,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.parsing.AndroidPackage; import android.os.Environment; import android.os.RemoteException; import android.os.ServiceManager; import android.sysprop.ApexProperties; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Singleton; import android.util.Slog; @@ -44,15 +47,20 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.google.android.collect.Lists; + import java.io.File; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -97,12 +105,27 @@ abstract class ApexManager { * Minimal information about APEX mount points and the original APEX package they refer to. */ static class ActiveApexInfo { + @Nullable public final String apexModuleName; public final File apexDirectory; - public final File preinstalledApexPath; + public final File preInstalledApexPath; + + private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) { + this(null, apexDirectory, preInstalledApexPath); + } - private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) { + private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory, + File preInstalledApexPath) { + this.apexModuleName = apexModuleName; this.apexDirectory = apexDirectory; - this.preinstalledApexPath = preinstalledApexPath; + this.preInstalledApexPath = preInstalledApexPath; + } + + private ActiveApexInfo(ApexInfo apexInfo) { + this( + apexInfo.moduleName, + new File(Environment.getApexDirectory() + File.separator + + apexInfo.moduleName), + new File(apexInfo.preinstalledModulePath)); } } @@ -232,6 +255,17 @@ abstract class ApexManager { abstract boolean uninstallApex(String apexPackagePath); /** + * Registers an APK package as an embedded apk of apex. + */ + abstract void registerApkInApex(AndroidPackage pkg); + + /** + * Returns list of {@code packageName} of apks inside the given apex. + * @param apexPackageName Package name of the apk container of apex + */ + abstract List<String> getApksInApex(String apexPackageName); + + /** * Dumps various state information to the provided {@link PrintWriter} object. * * @param pw the {@link PrintWriter} object to send information to. @@ -255,16 +289,33 @@ abstract class ApexManager { static class ApexManagerImpl extends ApexManager { private final IApexService mApexService; private final Object mLock = new Object(); + + @GuardedBy("mLock") + private Set<ActiveApexInfo> mActiveApexInfosCache; + /** - * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code - * AndroidManifest.xml} - * - * <p>Note that key of this map is {@code packageName} field of the corresponding {@code - * AndroidManifest.xml}. - */ + * Contains the list of {@code packageName}s of apks-in-apex for given + * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the + * difference between {@code packageName} and {@code apexModuleName}. + */ + @GuardedBy("mLock") + private Map<String, List<String>> mApksInApex = new ArrayMap<>(); + @GuardedBy("mLock") private List<PackageInfo> mAllPackagesCache; + /** + * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The + * apk container has a reference name, called {@code packageName}, which is found inside the + * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference + * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file. + * + * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of + * the apk container to {@code apexModuleName} of the apex-payload inside. + */ + @GuardedBy("mLock") + private Map<String, String> mPackageNameToApexModuleName; + ApexManagerImpl(IApexService apexService) { mApexService = apexService; } @@ -291,18 +342,25 @@ abstract class ApexManager { @Override List<ActiveApexInfo> getActiveApexInfos() { - try { - return Arrays.stream(mApexService.getActivePackages()) - .map(apexInfo -> new ActiveApexInfo( - new File( - Environment.getApexDirectory() + File.separator - + apexInfo.moduleName), - new File(apexInfo.preinstalledModulePath))).collect( - Collectors.toList()); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to retrieve packages from apexservice", e); + synchronized (mLock) { + if (mActiveApexInfosCache == null) { + try { + mActiveApexInfosCache = new ArraySet<>(); + final ApexInfo[] activePackages = mApexService.getActivePackages(); + for (int i = 0; i < activePackages.length; i++) { + ApexInfo apexInfo = activePackages[i]; + mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo)); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to retrieve packages from apexservice", e); + } + } + if (mActiveApexInfosCache != null) { + return new ArrayList<>(mActiveApexInfosCache); + } else { + return Collections.emptyList(); + } } - return Collections.emptyList(); } @Override @@ -325,6 +383,7 @@ abstract class ApexManager { } try { mAllPackagesCache = new ArrayList<>(); + mPackageNameToApexModuleName = new HashMap<>(); HashSet<String> activePackagesSet = new HashSet<>(); HashSet<String> factoryPackagesSet = new HashSet<>(); final ApexInfo[] allPkgs = mApexService.getAllPackages(); @@ -350,6 +409,7 @@ abstract class ApexManager { final PackageInfo packageInfo = PackageParser.generatePackageInfo(pkg, ai, flags); mAllPackagesCache.add(packageInfo); + mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName); if (ai.isActive) { if (activePackagesSet.contains(packageInfo.packageName)) { throw new IllegalStateException( @@ -366,7 +426,6 @@ abstract class ApexManager { } factoryPackagesSet.add(packageInfo.packageName); } - } } catch (RemoteException re) { Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); @@ -533,6 +592,37 @@ abstract class ApexManager { } } + @Override + void registerApkInApex(AndroidPackage pkg) { + synchronized (mLock) { + final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator(); + while (it.hasNext()) { + final ActiveApexInfo aai = it.next(); + if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) { + List<String> apks = mApksInApex.get(aai.apexModuleName); + if (apks == null) { + apks = Lists.newArrayList(); + mApksInApex.put(aai.apexModuleName, apks); + } + apks.add(pkg.getPackageName()); + } + } + } + } + + @Override + List<String> getApksInApex(String apexPackageName) { + // TODO(b/142712057): Avoid calling populateAllPackagesCacheIfNeeded during boot. + populateAllPackagesCacheIfNeeded(); + synchronized (mLock) { + String moduleName = mPackageNameToApexModuleName.get(apexPackageName); + if (moduleName == null) { + return Collections.emptyList(); + } + return mApksInApex.getOrDefault(moduleName, Collections.emptyList()); + } + } + /** * Dump information about the packages contained in a particular cache * @param packagesCache the cache to print information about. @@ -614,7 +704,6 @@ abstract class ApexManager { * updating APEX packages. */ private static final class ApexManagerFlattenedApex extends ApexManager { - @Override List<ActiveApexInfo> getActiveApexInfos() { // There is no apexd running in case of flattened apex @@ -721,6 +810,16 @@ abstract class ApexManager { } @Override + void registerApkInApex(AndroidPackage pkg) { + // No-op + } + + @Override + List<String> getApksInApex(String apexPackageName) { + return Collections.emptyList(); + } + + @Override void dump(PrintWriter pw, String packageName) { // No-op } diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index b25e1e2160c3..ed7139991937 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -15,35 +15,45 @@ */ package com.android.server.pm; +import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; +import android.Manifest; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ICrossProfileApps; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.stats.devicepolicy.DevicePolicyEnums; import android.text.TextUtils; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; +import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; +import com.android.server.appop.AppOpsService; import com.android.server.wm.ActivityTaskManagerInternal; import java.util.ArrayList; @@ -55,6 +65,9 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { private Context mContext; private Injector mInjector; + private AppOpsService mAppOpsService; + private final DevicePolicyManagerInternal mDpmi; + private final IPackageManager mIpm; public CrossProfileAppsServiceImpl(Context context) { this(context, new InjectorImpl(context)); @@ -64,6 +77,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { CrossProfileAppsServiceImpl(Context context, Injector injector) { mContext = context; mInjector = injector; + mIpm = AppGlobals.getPackageManager(); + mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class); } @Override @@ -153,6 +168,63 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { userId); } + @Override + public boolean canRequestInteractAcrossProfiles(String callingPackage) { + Objects.requireNonNull(callingPackage); + verifyCallingPackage(callingPackage); + + final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked( + callingPackage, mInjector.getCallingUserId()); + if (targetUserProfiles.isEmpty()) { + return false; + } + + if (!hasRequestedAppOpPermission( + AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), callingPackage)) { + return false; + } + return isCrossProfilePackageWhitelisted(callingPackage); + } + + private boolean hasRequestedAppOpPermission(String permission, String packageName) { + try { + String[] packages = mIpm.getAppOpPermissionPackages(permission); + return ArrayUtils.contains(packages, packageName); + } catch (RemoteException exc) { + Slog.e(TAG, "PackageManager dead. Cannot get permission info"); + return false; + } + } + + @Override + public boolean canInteractAcrossProfiles(String callingPackage) { + Objects.requireNonNull(callingPackage); + verifyCallingPackage(callingPackage); + + final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked( + callingPackage, mInjector.getCallingUserId()); + if (targetUserProfiles.isEmpty()) { + return false; + } + + final int callingUid = mInjector.getCallingUid(); + return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid) + || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid) + || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid) + || AppOpsManager.MODE_ALLOWED == getAppOpsService().noteOperation( + OP_INTERACT_ACROSS_PROFILES, callingUid, callingPackage, /* featureId= */ null, + /*shouldCollectAsyncNotedOp= */false, /*message= */null); + } + + private boolean isCrossProfilePackageWhitelisted(String packageName) { + final long ident = mInjector.clearCallingIdentity(); + try { + return mDpmi.getAllCrossProfilePackages().contains(packageName); + } finally { + mInjector.restoreCallingIdentity(ident); + } + } + private List<UserHandle> getTargetUserProfilesUnchecked( String callingPackage, @UserIdInt int callingUserId) { final long ident = mInjector.clearCallingIdentity(); @@ -239,6 +311,19 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage); } + private static boolean isPermissionGranted(String permission, int uid) { + return PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission( + permission, uid, /* owningUid= */-1, /* exported= */ true); + } + + private AppOpsService getAppOpsService() { + if (mAppOpsService == null) { + IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); + mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b); + } + return mAppOpsService; + } + private static class InjectorImpl implements Injector { private Context mContext; diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java index 0541797523a4..6684e3f8973e 100644 --- a/services/core/java/com/android/server/pm/InstallSource.java +++ b/services/core/java/com/android/server/pm/InstallSource.java @@ -18,6 +18,8 @@ package com.android.server.pm; import android.annotation.Nullable; +import com.android.internal.util.Preconditions; + import java.util.Objects; /** @@ -29,16 +31,27 @@ final class InstallSource { * An instance of InstallSource representing an absence of knowledge of the source of * a package. Used in preference to null. */ - static final InstallSource EMPTY = new InstallSource(null, null, null, false); + static final InstallSource EMPTY = new InstallSource(null, null, null, false, false, null); /** We also memoize this case because it is common - all un-updated system apps. */ - private static final InstallSource EMPTY_ORPHANED = new InstallSource(null, null, null, true); + private static final InstallSource EMPTY_ORPHANED = new InstallSource( + null, null, null, true, false, null); - /** The package that requested the installation, if known. */ + /** + * The package that requested the installation, if known. May not correspond to a currently + * installed package if {@link #isInitiatingPackageUninstalled} is true. + */ @Nullable final String initiatingPackageName; /** + * The signing details of the initiating package, if known. Always null if + * {@link #initiatingPackageName} is null. + */ + @Nullable + final PackageSignatures initiatingPackageSignatures; + + /** * The package on behalf of which the initiating package requested the installation, if any. * For example if a downloaded APK is installed via the Package Installer this could be the * app that performed the download. This value is provided by the initiating package and not @@ -57,76 +70,120 @@ final class InstallSource { /** Indicates if the package that was the installerPackageName has been uninstalled. */ final boolean isOrphaned; + /** + * Indicates if the package in initiatingPackageName has been uninstalled. Always false if + * {@link #initiatingPackageName} is null. + */ + final boolean isInitiatingPackageUninstalled; + + static InstallSource create(@Nullable String initiatingPackageName, + @Nullable String originatingPackageName, @Nullable String installerPackageName) { + return create(initiatingPackageName, originatingPackageName, installerPackageName, + false, false); + } + static InstallSource create(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled) { return createInternal( intern(initiatingPackageName), intern(originatingPackageName), intern(installerPackageName), - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, null); } private static InstallSource createInternal(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled, + @Nullable PackageSignatures initiatingPackageSignatures) { if (initiatingPackageName == null && originatingPackageName == null - && installerPackageName == null) { + && installerPackageName == null && initiatingPackageSignatures == null + && !isInitiatingPackageUninstalled) { return isOrphaned ? EMPTY_ORPHANED : EMPTY; } return new InstallSource(initiatingPackageName, originatingPackageName, - installerPackageName, isOrphaned); + installerPackageName, isOrphaned, isInitiatingPackageUninstalled, + initiatingPackageSignatures + ); } private InstallSource(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled, + @Nullable PackageSignatures initiatingPackageSignatures) { + if (initiatingPackageName == null) { + Preconditions.checkArgument(initiatingPackageSignatures == null); + Preconditions.checkArgument(!isInitiatingPackageUninstalled); + } this.initiatingPackageName = initiatingPackageName; this.originatingPackageName = originatingPackageName; this.installerPackageName = installerPackageName; this.isOrphaned = isOrphaned; + this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled; + this.initiatingPackageSignatures = initiatingPackageSignatures; } /** - * Return an InstallSource the same as this one except with the specified installerPackageName. + * Return an InstallSource the same as this one except with the specified + * {@link #installerPackageName}. */ - InstallSource setInstallerPackage(String installerPackageName) { + InstallSource setInstallerPackage(@Nullable String installerPackageName) { if (Objects.equals(installerPackageName, this.installerPackageName)) { return this; } return createInternal(initiatingPackageName, originatingPackageName, - intern(installerPackageName), isOrphaned); + intern(installerPackageName), isOrphaned, isInitiatingPackageUninstalled, + initiatingPackageSignatures + ); } /** - * Return an InstallSource the same as this one except with the specified value for isOrphaned. + * Return an InstallSource the same as this one except with the specified value for + * {@link #isOrphaned}. */ InstallSource setIsOrphaned(boolean isOrphaned) { if (isOrphaned == this.isOrphaned) { return this; } return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures); } /** - * Return an InstallSource the same as this one except it does not refer to the specified - * installer package name (which is being uninstalled). + * Return an InstallSource the same as this one except with the specified + * {@link #initiatingPackageSignatures}. */ - InstallSource removeInstallerPackage(String packageName) { + InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) { + if (signatures == initiatingPackageSignatures) { + return this; + } + return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, + isOrphaned, isInitiatingPackageUninstalled, signatures); + } + + /** + * Return an InstallSource the same as this one updated to reflect that the specified installer + * package name has been uninstalled. + */ + InstallSource removeInstallerPackage(@Nullable String packageName) { if (packageName == null) { return this; } boolean modified = false; - String initiatingPackageName = this.initiatingPackageName; + boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled; String originatingPackageName = this.originatingPackageName; String installerPackageName = this.installerPackageName; boolean isOrphaned = this.isOrphaned; - if (packageName.equals(initiatingPackageName)) { - initiatingPackageName = null; - modified = true; + if (packageName.equals(this.initiatingPackageName)) { + if (!isInitiatingPackageUninstalled) { + // In this case we deliberately do not clear the package name (and signatures). + // We allow an app to retrieve details of its own install initiator even after + // it has been uninstalled. + isInitiatingPackageUninstalled = true; + modified = true; + } } if (packageName.equals(originatingPackageName)) { originatingPackageName = null; @@ -141,8 +198,9 @@ final class InstallSource { if (!modified) { return this; } + return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures); } @Nullable diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index e2dfa126225f..6331dd46c035 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -188,7 +188,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } }; - public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) { + public PackageInstallerService(Context context, PackageManagerService pm) { mContext = context; mPm = pm; mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class); @@ -206,9 +206,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions"); mSessionsDir.mkdirs(); - mApexManager = am; - - mStagingManager = new StagingManager(this, am, context); + mApexManager = ApexManager.getInstance(); + mStagingManager = new StagingManager(this, context); } boolean okToSendBroadcasts() { @@ -635,7 +634,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } InstallSource installSource = InstallSource.create(installerPackageName, - originatingPackageName, requestedInstallerPackageName, false); + originatingPackageName, requestedInstallerPackageName); session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 4dcdf7ea0ecc..165bdebe070f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1475,7 +1475,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mInstallerUid = uid; - mInstallSource = InstallSource.create(packageName, null, packageName, false); + mInstallSource = InstallSource.create(packageName, null, packageName); } } catch (PackageManager.NameNotFoundException e) { onSessionTransferStatus(statusReceiver, packageName, @@ -2413,16 +2413,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void addFile(String name, long lengthBytes, byte[] metadata) { - if (mIncrementalFileStorages != null) { - try { - mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata)); - //TODO(b/136132412): merge incremental and callback installation schemes - return; - } catch (IOException ex) { - throw new IllegalStateException( - "Failed to add and configure Incremental File: " + name, ex); - } - } if (!isDataLoaderInstallation()) { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); @@ -2435,7 +2425,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("addFile"); - mFiles.add(FileInfo.added(name, lengthBytes, metadata)); } } @@ -2486,7 +2475,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ private void prepareDataLoader() throws PackageManagerException, StreamingException { - if (!isStreamingInstallation()) { + if (!isDataLoaderInstallation()) { return; } @@ -2500,6 +2489,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { file -> file.name.substring( 0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect( Collectors.toList()); + if (mIncrementalFileStorages != null) { + for (InstallationFile file : addedFiles) { + try { + mIncrementalFileStorages.addFile(file); + } catch (IOException ex) { + // TODO(b/146080380): add incremental-specific error code + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Failed to add and configure Incremental File: " + file.getName(), ex); + } + } + return; + } final FileSystemConnector connector = new FileSystemConnector(addedFiles); @@ -3138,7 +3139,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } InstallSource installSource = InstallSource.create(installInitiatingPackageName, - installOriginatingPackageName, installerPackageName, false); + installOriginatingPackageName, installerPackageName); return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, installSource, params, createdMillis, stageDir, stageCid, fileInfosArray, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6bd9c4847a77..17870ebe9957 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -492,6 +492,7 @@ public class PackageManagerService extends IPackageManager.Stub static final int SCAN_AS_PRODUCT = 1 << 20; static final int SCAN_AS_SYSTEM_EXT = 1 << 21; static final int SCAN_AS_ODM = 1 << 22; + static final int SCAN_AS_APK_IN_APEX = 1 << 23; @IntDef(flag = true, prefix = { "SCAN_" }, value = { SCAN_NO_DEX, @@ -2589,6 +2590,9 @@ public class PackageManagerService extends IPackageManager.Stub & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) { return true; } + if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { + return true; + } return false; } @@ -3332,7 +3336,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - mInstallerService = new PackageInstallerService(mContext, this, mApexManager); + mInstallerService = new PackageInstallerService(mContext, this); final Pair<ComponentName, String> instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { @@ -5344,8 +5348,9 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForComponent(flags, userId); - mPermissionManager.enforceCrossUserPermission(callingUid, userId, - false /* requireFullPermission */, false /* checkShell */, "get service info"); + mPermissionManager.enforceCrossUserOrProfilePermission( + callingUid, userId, false /* requireFullPermission */, false /* checkShell */, + "get service info"); synchronized (mLock) { ParsedService s = mComponentResolver.getService(component); if (DEBUG_PACKAGE_INFO) Log.v( @@ -7795,8 +7800,10 @@ public class PackageManagerService extends IPackageManager.Stub String resolvedType, int flags, int userId, int callingUid, boolean includeInstantApps) { if (!mUserManager.exists(userId)) return Collections.emptyList(); - mPermissionManager.enforceCrossUserPermission(callingUid, userId, - false /*requireFullPermission*/, false /*checkShell*/, + mPermissionManager.enforceCrossUserOrProfilePermission(callingUid, + userId, + false /*requireFullPermission*/, + false /*checkShell*/, "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps); @@ -11710,6 +11717,9 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.insertPackageSettingLPw(pkgSetting, pkg); // Add the new setting to mPackages mPackages.put(pkg.getAppInfoPackageName(), pkg); + if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { + mApexManager.registerApkInApex(pkg); + } // Add the package's KeySets to the global KeySetManagerService KeySetManagerService ksms = mSettings.mKeySetManagerService; @@ -15158,10 +15168,10 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); final String pkgName = pkg.getPackageName(); - final InstallSource installSource = installArgs.installSource; - final String installerPackageName = installSource.installerPackageName; final int[] installedForUsers = res.origUsers; final int installReason = installArgs.installReason; + InstallSource installSource = installArgs.installSource; + final String installerPackageName = installSource.installerPackageName; if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getCodePath()); synchronized (mLock) { @@ -15207,6 +15217,14 @@ public class PackageManagerService extends IPackageManager.Stub ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); } + if (installSource.initiatingPackageName != null) { + final PackageSetting ips = mSettings.mPackages.get( + installSource.initiatingPackageName); + if (ips != null) { + installSource = installSource.setInitiatingPackageSignatures( + ips.signatures); + } + } ps.setInstallSource(installSource); mSettings.addInstallerPackageNames(installSource); @@ -17749,10 +17767,10 @@ public class PackageManagerService extends IPackageManager.Stub ApexManager.ActiveApexInfo apexInfo) { for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) { SystemPartition sp = SYSTEM_PARTITIONS.get(i); - if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith( + if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith( sp.folder.getAbsolutePath())) { - return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag, - false /* hasOverlays */); + return new SystemPartition(apexInfo.apexDirectory, + sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */); } } return null; @@ -19372,7 +19390,7 @@ public class PackageManagerService extends IPackageManager.Stub // PermissionController manages default home directly. return false; } - mPermissionManager.setDefaultHome(currentPackageName, userId, (successful) -> { + mPermissionManager.setDefaultHome(packageName, userId, (successful) -> { if (successful) { postPreferredActivityChangedBroadcast(userId); } @@ -19976,19 +19994,25 @@ public class PackageManagerService extends IPackageManager.Stub } } - // All installSource strings are interned, so == is ok here - if (installSource.initiatingPackageName == installSource.installerPackageName) { - // The installer and initiator will often be the same, and when they are - // we can skip doing the same check again. - initiatingPackageName = installerPackageName; + if (installSource.isInitiatingPackageUninstalled) { + // TODO(b/146555198) Allow the app itself to see the info + // (at least for non-instant apps) + initiatingPackageName = null; } else { - initiatingPackageName = installSource.initiatingPackageName; - final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName); - if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { - initiatingPackageName = null; + // All installSource strings are interned, so == is ok here + if (installSource.initiatingPackageName == installSource.installerPackageName) { + // The installer and initiator will often be the same, and when they are + // we can skip doing the same check again. + initiatingPackageName = installerPackageName; + } else { + initiatingPackageName = installSource.initiatingPackageName; + final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName); + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { + initiatingPackageName = null; + } } - } + originatingPackageName = installSource.originatingPackageName; if (originatingPackageName != null) { final PackageSetting ps = mSettings.mPackages.get(originatingPackageName); @@ -20108,8 +20132,7 @@ public class PackageManagerService extends IPackageManager.Stub // Disable any carrier apps. We do this very early in boot to prevent the apps from being // disabled after already being started. CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this, - mPermissionManagerService, mContext.getContentResolver(), - UserHandle.USER_SYSTEM); + mPermissionManagerService, UserHandle.USER_SYSTEM, mContext); disableSkuSpecificApps(); @@ -22777,7 +22800,7 @@ public class PackageManagerService extends IPackageManager.Stub ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length); for (String pkgName: pkgNames) { - synchronized (mPackages) { + synchronized (mLock) { if (pkgName == null) { continue; } @@ -23438,6 +23461,11 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public List<String> getApksInApex(String apexPackageName) { + return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName); + } + + @Override public void uninstallApex(String packageName, long versionCode, int userId, IntentSender intentSender, int flags) { final int callerUid = Binder.getCallingUid(); @@ -23618,7 +23646,7 @@ public class PackageManagerService extends IPackageManager.Stub @Nullable public PackageSetting getPackageSetting(String packageName) { - synchronized (mPackages) { + synchronized (mLock) { packageName = resolveInternalPackageNameLPr( packageName, PackageManager.VERSION_CODE_HIGHEST); return mSettings.mPackages.get(packageName); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index f9a336166825..ec84b51577f9 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2819,6 +2819,9 @@ public final class Settings { if (installSource.initiatingPackageName != null) { serializer.attribute(null, "installInitiator", installSource.initiatingPackageName); } + if (installSource.isInitiatingPackageUninstalled) { + serializer.attribute(null, "installInitiatorUninstalled", "true"); + } if (installSource.originatingPackageName != null) { serializer.attribute(null, "installOriginator", installSource.originatingPackageName); } @@ -2836,6 +2839,11 @@ public final class Settings { pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); + if (installSource.initiatingPackageSignatures != null) { + installSource.initiatingPackageSignatures.writeXml( + serializer, "install-initiator-sigs", mPastSignatures); + } + writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates()); writeSigningKeySetLPr(serializer, pkg.keySetData); @@ -3571,6 +3579,7 @@ public final class Settings { String isOrphaned = null; String installOriginatingPackageName = null; String installInitiatingPackageName = null; + String installInitiatorUninstalled = null; String volumeUuid = null; String categoryHintString = null; String updateAvailable = null; @@ -3616,6 +3625,8 @@ public final class Settings { isOrphaned = parser.getAttributeValue(null, "isOrphaned"); installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator"); installOriginatingPackageName = parser.getAttributeValue(null, "installOriginator"); + installInitiatorUninstalled = parser.getAttributeValue(null, + "installInitiatorUninstalled"); volumeUuid = parser.getAttributeValue(null, "volumeUuid"); categoryHintString = parser.getAttributeValue(null, "categoryHint"); if (categoryHintString != null) { @@ -3772,7 +3783,8 @@ public final class Settings { packageSetting.uidError = "true".equals(uidError); InstallSource installSource = InstallSource.create( installInitiatingPackageName, installOriginatingPackageName, - installerPackageName, "true".equals(isOrphaned)); + installerPackageName, "true".equals(isOrphaned), + "true".equals(installInitiatorUninstalled)); packageSetting.installSource = installSource; packageSetting.volumeUuid = volumeUuid; packageSetting.categoryHint = categoryHint; @@ -3849,6 +3861,11 @@ public final class Settings { mKeySetRefs.put(id, 1); } packageSetting.keySetData.addDefinedKeySet(id, alias); + } else if (tagName.equals("install-initiator-sigs")) { + final PackageSignatures signatures = new PackageSignatures(); + signatures.readXml(parser, mPastSignatures); + packageSetting.installSource = + packageSetting.installSource.setInitiatingPackageSignatures(signatures); } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) { readDomainVerificationLPw(parser, packageSetting); } else { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 9e462cd529bb..2265d010216e 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -33,11 +33,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.ParceledListSlice; +import android.content.pm.parsing.AndroidPackage; import android.content.rollback.IRollbackManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; @@ -50,6 +52,8 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.util.IntArray; @@ -61,6 +65,7 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; import java.io.File; import java.io.IOException; @@ -93,10 +98,11 @@ public class StagingManager { @GuardedBy("mStagedSessions") private final SparseIntArray mSessionRollbackIds = new SparseIntArray(); - StagingManager(PackageInstallerService pi, ApexManager am, Context context) { + StagingManager(PackageInstallerService pi, Context context) { mPi = pi; - mApexManager = am; mContext = context; + + mApexManager = ApexManager.getInstance(); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPreRebootVerificationHandler = new PreRebootVerificationHandler( BackgroundThread.get().getLooper()); @@ -334,6 +340,88 @@ public class StagingManager { return PackageHelper.getStorageManager().needsCheckpoint(); } + /** + * Apks inside apex are not installed using apk-install flow. They are scanned from the system + * directory directly by PackageManager, as such, RollbackManager need to handle their data + * separately here. + */ + private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) { + // We want to process apks inside apex. So current session needs to contain apex. + if (!sessionContainsApex(session)) { + return; + } + + boolean doSnapshotOrRestore = + (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 + || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; + if (!doSnapshotOrRestore) { + return; + } + + // Find all the apex sessions that needs processing + List<PackageInstallerSession> apexSessions = new ArrayList<>(); + if (session.isMultiPackage()) { + List<PackageInstallerSession> childrenSessions = new ArrayList<>(); + synchronized (mStagedSessions) { + for (int childSessionId : session.getChildSessionIds()) { + PackageInstallerSession childSession = mStagedSessions.get(childSessionId); + if (childSession != null) { + childrenSessions.add(childSession); + } + } + } + for (PackageInstallerSession childSession : childrenSessions) { + if (sessionContainsApex(childSession)) { + apexSessions.add(childSession); + } + } + } else { + apexSessions.add(session); + } + + // For each apex, process the apks inside it + for (PackageInstallerSession apexSession : apexSessions) { + List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName()); + for (String apk: apksInApex) { + snapshotAndRestoreApkInApexUserData(apk); + } + } + } + + private void snapshotAndRestoreApkInApexUserData(String packageName) { + IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + + PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class); + AndroidPackage pkg = mPmi.getPackage(packageName); + if (pkg == null) { + Slog.e(TAG, "Could not find package: " + packageName + + "for snapshotting/restoring user data."); + return; + } + final String seInfo = pkg.getSeInfo(); + final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); + final int[] allUsers = um.getUserIds(); + + int appId = -1; + long ceDataInode = -1; + final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName); + if (ps != null && rm != null) { + appId = ps.appId; + ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM); + // NOTE: We ignore the user specified in the InstallParam because we know this is + // an update, and hence need to restore data for all installed users. + final int[] installedUsers = ps.queryInstalledUsers(allUsers, true); + + try { + rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode, + seInfo, 0 /*token*/); + } catch (RemoteException re) { + Slog.e(TAG, "Error snapshotting/restoring user data: " + re); + } + } + } + private void resumeSession(@NonNull PackageInstallerSession session) { Slog.d(TAG, "Resuming session " + session.sessionId); @@ -407,6 +495,7 @@ public class StagingManager { abortCheckpoint(); return; } + snapshotAndRestoreApkInApexUserData(session); Slog.i(TAG, "APEX packages in session " + session.sessionId + " were successfully activated. Proceeding with APK packages, if any"); } @@ -529,7 +618,7 @@ public class StagingManager { Arrays.stream(session.getChildSessionIds()) // Retrieve cached sessions matching ids. .mapToObj(i -> mStagedSessions.get(i)) - // Filter only the ones containing APKs.s + // Filter only the ones containing APKs. .filter(childSession -> !isApexSession(childSession)) .collect(Collectors.toList()); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index d921f313eb48..d8c196674c59 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -4005,23 +4005,130 @@ public class PermissionManagerService extends IPermissionManager.Stub { PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); } - if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return; - if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) { - if (requireFullPermission) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - } else { - try { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - } catch (SecurityException se) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, message); - } - } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission( + callingUid, callingUserId, userId, requireFullPermission, + requirePermissionWhenSameUser)) { + return; + } + String errorMessage = buildInvalidCrossUserPermissionMessage( + message, requireFullPermission); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + /** + * Checks if the request is from the system or an app that has the appropriate cross-user + * permissions defined as follows: + * <ul> + * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li> + * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group + * to the caller.</li> + * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group + * as the caller.</li> + * </ul> + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param message the message to log on security exception + */ + private void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, + String message) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid userId " + userId); + } + if (checkShell) { + PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission, + /*requirePermissionWhenSameUser= */ false)) { + return; + } + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); + if (isSameProfileGroup + && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) { + return; + } + String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( + message, requireFullPermission, isSameProfileGroup); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + private boolean hasCrossUserPermission( + int callingUid, int callingUserId, int userId, boolean requireFullPermission, + boolean requirePermissionWhenSameUser) { + if (!requirePermissionWhenSameUser && userId == callingUserId) { + return true; + } + if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { + return true; + } + if (requireFullPermission) { + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + } + + private boolean hasPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId); + } finally { + Binder.restoreCallingIdentity(identity); } } + private static String buildInvalidCrossUserPermissionMessage( + String message, boolean requireFullPermission) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message); + builder.append(": "); + } + builder.append("Requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (requireFullPermission) { + builder.append("."); + return builder.toString(); + } + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + builder.append("."); + return builder.toString(); + } + + private static String buildInvalidCrossUserOrProfilePermissionMessage( + String message, boolean requireFullPermission, boolean isSameProfileGroup) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message); + builder.append(": "); + } + builder.append("Requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (requireFullPermission) { + builder.append("."); + return builder.toString(); + } + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + if (isSameProfileGroup) { + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES); + } + builder.append("."); + return builder.toString(); + } + @GuardedBy({"mSettings.mLock", "mLock"}) private int calculateCurrentPermissionFootprintLocked(BasePermission tree) { int size = 0; @@ -4215,6 +4322,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, requirePermissionWhenSameUser, message); } + + @Override + public void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, String message) { + PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid, + userId, + requireFullPermission, + checkShell, + message); + } + @Override public void enforceGrantRevokeRuntimePermissionPermissions(String message) { PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 0f22619fafa6..58a9f42f372d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -271,6 +271,15 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager */ public abstract void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission, boolean checkShell, @NonNull String message); + + /** + * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)} + * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are + * in the same profile group. + */ + public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, @NonNull String message); + /** * @see #enforceCrossUserPermission(int, int, boolean, boolean, String) * @param requirePermissionWhenSameUser When {@code true}, still require the cross user diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index e7269a6d9498..a86c8d7545b1 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -58,6 +58,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.IntPair; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; @@ -68,7 +69,7 @@ import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; /** * This is a permission policy that governs over all permission mechanism @@ -280,7 +281,7 @@ public final class PermissionPolicyService extends SystemService { if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")"); // Now call into the permission controller to apply policy around permissions - final CountDownLatch latch = new CountDownLatch(1); + final AndroidFuture<Boolean> future = new AndroidFuture<>(); // We need to create a local manager that does not schedule work on the main // there as we are on the main thread and want to block until the work is @@ -290,22 +291,22 @@ public final class PermissionPolicyService extends SystemService { getUserContext(getContext(), UserHandle.of(userId)), FgThread.getHandler()); permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions( - FgThread.getExecutor(), - (Boolean success) -> { - if (!success) { + FgThread.getExecutor(), successful -> { + if (successful) { + future.complete(null); + } else { // We are in an undefined state now, let us crash and have // rescue party suggest a wipe to recover to a good one. - final String message = "Error granting/upgrading runtime permissions"; + final String message = "Error granting/upgrading runtime permissions" + + " for user " + userId; Slog.wtf(LOG_TAG, message); - throw new IllegalStateException(message); + future.completeExceptionally(new IllegalStateException(message)); } - latch.countDown(); - } - ); + }); try { - latch.await(); - } catch (InterruptedException e) { - /* ignore */ + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); } permissionControllerManager.updateUserSensitive(); diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 88c1564fdb60..9f592b85a5e6 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -62,7 +62,7 @@ class Rollback { private static final String TAG = "RollbackManager"; - @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = { + @IntDef(prefix = { "ROLLBACK_STATE_" }, value = { ROLLBACK_STATE_ENABLING, ROLLBACK_STATE_AVAILABLE, ROLLBACK_STATE_COMMITTED, @@ -92,6 +92,19 @@ class Rollback { */ static final int ROLLBACK_STATE_DELETED = 4; + @IntDef(flag = true, prefix = { "MATCH_" }, value = { + MATCH_APK_IN_APEX, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RollbackInfoFlags {} + + /** + * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex. + * These packages do not have their own sessions. They are embedded in an apex which has a + * session id. + */ + static final int MATCH_APK_IN_APEX = 1; + /** * The session ID for the staged session if this rollback data represents a staged session, * {@code -1} otherwise. @@ -323,8 +336,8 @@ class Rollback { new VersionedPackage(packageName, newVersion), new VersionedPackage(packageName, installedVersion), new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */, - isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */, - rollbackDataPolicy); + isApex, false /* isApkInApex */, new IntArray(), + new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy); synchronized (mLock) { info.getPackages().add(packageRollbackInfo); @@ -334,6 +347,30 @@ class Rollback { } /** + * Enables this rollback for the provided apk-in-apex. + * + * @return boolean True if the rollback was enabled successfully for the specified package. + */ + boolean enableForPackageInApex(String packageName, long installedVersion, + int rollbackDataPolicy) { + // TODO(b/142712057): Extract the new version number of apk-in-apex + // The new version for the apk-in-apex is set to 0 for now. If the package is then further + // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced() + // will be called and this rollback will be deleted. Other ways of package update have not + // been handled yet. + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo( + new VersionedPackage(packageName, 0 /* newVersion */), + new VersionedPackage(packageName, installedVersion), + new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */, + false /* isApex */, true /* isApkInApex */, new IntArray(), + new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy); + synchronized (mLock) { + info.getPackages().add(packageRollbackInfo); + } + return true; + } + + /** * Snapshots user data for the provided package and user ids. Does nothing if this rollback is * not in the ENABLING state. */ @@ -428,6 +465,11 @@ class Rollback { parentSessionId); for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) { + if (pkgRollbackInfo.isApkInApex()) { + // No need to issue a downgrade install request for apk-in-apex. It will + // be rolled back when its parent apex is downgraded. + continue; + } PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); String installerPackageName = mInstallerPackageName; @@ -453,7 +495,8 @@ class Rollback { this, pkgRollbackInfo.getPackageName()); if (packageCodePaths == null) { sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE, - "Backup copy of package inaccessible"); + "Backup copy of package: " + + pkgRollbackInfo.getPackageName() + " is inaccessible"); return; } @@ -696,9 +739,30 @@ class Rollback { } } - int getPackageCount() { + /** + * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback} + * instance. By default, this method does not include apk-in-apex package in the count. + * + * @param flags Apk-in-apex packages can be included in the count by passing + * {@link Rollback#MATCH_APK_IN_APEX} + * + * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback} + * according to {@code flags} passed + */ + int getPackageCount(@RollbackInfoFlags int flags) { synchronized (mLock) { - return info.getPackages().size(); + List<PackageRollbackInfo> packages = info.getPackages(); + if ((flags & MATCH_APK_IN_APEX) != 0) { + return packages.size(); + } + + int packagesWithoutApkInApex = 0; + for (PackageRollbackInfo rollbackInfo : packages) { + if (!rollbackInfo.isApkInApex()) { + packagesWithoutApkInApex++; + } + } + return packagesWithoutApkInApex; } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index e29d1a765d69..8f8a5c4b14e9 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -891,9 +891,36 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } ApplicationInfo appInfo = pkgInfo.applicationInfo; - return rollback.enableForPackage(packageName, newPackage.versionCode, + boolean success = rollback.enableForPackage(packageName, newPackage.versionCode, pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir, appInfo.splitSourceDirs, session.rollbackDataPolicy); + if (!success) { + return success; + } + + if (isApex) { + // Check if this apex contains apks inside it. If true, then they should be added as + // a RollbackPackageInfo into this rollback + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + List<String> apksInApex = pmi.getApksInApex(packageName); + for (String apkInApex : apksInApex) { + // Get information about the currently installed package. + final PackageInfo apkPkgInfo; + try { + apkPkgInfo = getPackageInfo(apkInApex); + } catch (PackageManager.NameNotFoundException e) { + // TODO: Support rolling back fresh package installs rather than + // fail here. Test this case. + Slog.e(TAG, apkInApex + " is not installed"); + return false; + } + success = rollback.enableForPackageInApex( + apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy); + if (!success) return success; + } + } + return true; } @Override @@ -907,9 +934,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { snapshotUserDataInternal(packageName, userIds); restoreUserDataInternal(packageName, userIds, appId, seInfo); - final PackageManagerInternal pmi = LocalServices.getService( - PackageManagerInternal.class); - pmi.finishPackageInstall(token, false); + // When this method is called as part of the install flow, a positive token number is + // passed to it. Need to notify the PackageManager when we are done. + if (token > 0) { + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + pmi.finishPackageInstall(token, false); + } }); } @@ -1195,7 +1226,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return null; } - if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) { + // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is + // equal to the number of sessions we are installing, to ensure we didn't skip enabling + // of any sessions. If we successfully enable an apex, then we can assume we enabled + // rollback for the embedded apk-in-apex, if any. + if (rollback.getPackageCount(0 /*flags*/) != newRollback.getPackageSessionIdCount()) { Slog.e(TAG, "Failed to enable rollback for all packages in session."); rollback.delete(mAppDataRollbackHelper); return null; diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index df75a29edd79..bbcd0def05a8 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -341,6 +341,7 @@ class RollbackStore { json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("isApex", info.isApex()); + json.put("isApkInApex", info.isApkInApex()); // Field is named 'installedUsers' for legacy reasons. json.put("installedUsers", convertToJsonArray(snapshottedUsers)); @@ -364,6 +365,7 @@ class RollbackStore { json.getJSONArray("pendingRestores")); final boolean isApex = json.getBoolean("isApex"); + final boolean isApkInApex = json.getBoolean("isApkInApex"); // Field is named 'installedUsers' for legacy reasons. final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers")); @@ -375,8 +377,8 @@ class RollbackStore { PackageManager.RollbackDataPolicy.RESTORE); return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, - pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes, - rollbackDataPolicy); + pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers, + ceSnapshotInodes, rollbackDataPolicy); } private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages) diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java index 0b89646bbed1..8385f406f13d 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.hardware.audio.common.V2_0.Uuid; import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback; import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; +import android.hardware.soundtrigger.V2_3.Properties; import android.media.audio.common.AudioConfig; import android.media.audio.common.AudioOffloadInfo; import android.media.soundtrigger_middleware.ConfidenceLevel; @@ -69,6 +70,13 @@ class ConversionUtil { return aidlProperties; } + static @NonNull SoundTriggerModuleProperties hidl2aidlProperties( + @NonNull Properties hidlProperties) { + SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base); + aidlProperties.supportedModelArch = hidlProperties.supportedModelArch; + return aidlProperties; + } + static @NonNull String hidl2aidlUuid(@NonNull Uuid hidlUuid) { if (hidlUuid.node == null || hidlUuid.node.length != 6) { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java index f0a0d8305bc6..dbf91a984bda 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java @@ -74,4 +74,12 @@ class Hw2CompatUtil { config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data); return config_2_0; } + + static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3( + android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) { + android.hardware.soundtrigger.V2_3.Properties properties_2_3 = + new android.hardware.soundtrigger.V2_3.Properties(); + properties_2_3.base = properties; + return properties_2_3; + } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java index 81252c9a8c14..2f024a50a276 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java @@ -16,7 +16,6 @@ package com.android.server.soundtrigger_middleware; -import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; import android.hardware.soundtrigger.V2_3.ModelParameterRange; import android.hidl.base.V1_0.IBase; import android.os.IHwBinder; @@ -54,9 +53,10 @@ import android.os.IHwBinder; */ public interface ISoundTriggerHw2 { /** - * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback + * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx( + * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback) */ - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties(); + android.hardware.soundtrigger.V2_3.Properties getProperties(); /** * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel, diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java index 4a852c4b68e8..3354c561b57a 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java @@ -112,18 +112,23 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { } @Override - public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() { + public android.hardware.soundtrigger.V2_3.Properties getProperties() { try { AtomicInteger retval = new AtomicInteger(-1); - AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties> + AtomicReference<android.hardware.soundtrigger.V2_3.Properties> properties = new AtomicReference<>(); - as2_0().getProperties( - (r, p) -> { - retval.set(r); - properties.set(p); - }); - handleHalStatus(retval.get(), "getProperties"); + try { + as2_3().getProperties_2_3( + (r, p) -> { + retval.set(r); + properties.set(p); + }); + } catch (NotSupported e) { + // Fall-back to the 2.0 version: + return getProperties_2_0(); + } + handleHalStatus(retval.get(), "getProperties_2_3"); return properties.get(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); @@ -312,6 +317,21 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { return as2_0().interfaceDescriptor(); } + private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0() + throws RemoteException { + AtomicInteger retval = new AtomicInteger(-1); + AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties> + properties = + new AtomicReference<>(); + as2_0().getProperties( + (r, p) -> { + retval.set(r); + properties.set(p); + }); + handleHalStatus(retval.get(), "getProperties"); + return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get()); + } + private int loadSoundModel_2_0( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel, Callback callback, int cookie) diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 987c05fd8c9d..5a06a2c4b388 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.PermissionChecker; import android.hardware.soundtrigger.V2_0.ISoundTriggerHw; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; @@ -32,6 +33,7 @@ import android.media.soundtrigger_middleware.RecognitionEvent; import android.media.soundtrigger_middleware.RecognitionStatus; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; +import android.media.soundtrigger_middleware.Status; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -223,23 +225,48 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic } /** - * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this - * service. + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the right permissions to use this service. */ private void checkPermissions() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO, - "Caller must have the android.permission.RECORD_AUDIO permission."); - mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, - "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission."); + enforcePermission(Manifest.permission.RECORD_AUDIO); + enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); } /** - * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt - * active sound trigger sessions. + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the right permissions to preempt active sound trigger + * sessions. */ private void checkPreemptPermissions() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER, - "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission."); + enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER); + } + + /** + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the given permission. + * + * @param permission The permission to check. + */ + private void enforcePermission(String permission) { + final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext, + permission); + switch (status) { + case PermissionChecker.PERMISSION_GRANTED: + return; + case PermissionChecker.PERMISSION_DENIED: + throw new SecurityException( + String.format("Caller must have the %s permission.", permission)); + case PermissionChecker.PERMISSION_DENIED_APP_OP: + throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED, + String.format("Caller must have the %s permission.", permission)); + default: + throw new InternalServerError( + new RuntimeException("Unexpected perimission check result.")); + } } /** State of a sound model. */ diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index c95943904d1f..9fd3ea4fc090 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -163,6 +163,7 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.ITaskOrganizer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -803,6 +804,16 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, false /* creating */); + + windowingMode = getWindowingMode(); + /* + * Different windowing modes may be managed by different task organizers. If + * getTaskOrganizer returns null, we still call transferToTaskOrganizer to + * make sure we clear it. + */ + final ITaskOrganizer org = + mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + transferToTaskOrganizer(org); } /** @@ -1650,6 +1661,33 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } /** + * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't + * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking + * the first one is ok. + */ + boolean isControlledByTaskOrganizer() { + return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null; + } + + private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) { + tr.setTaskOrganizer(organizer); + } + + /** + * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer. + * This will (or shortly there-after) invoke the taskAppeared callbacks. + * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of + * emitting the taskVanished callbacks. + */ + void transferToTaskOrganizer(ITaskOrganizer organizer) { + final PooledConsumer c = PooledLambda.obtainConsumer( + ActivityStack::transferSingleTaskToOrganizer, + PooledLambda.__(Task.class), organizer); + forAllTasks(c); + c.recycle(); + } + + /** * Returns true if the stack should be visible. * * @param starting The currently starting activity or null if there is none. @@ -3577,6 +3615,15 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration, boolean fromFullscreen) { if (!inPinnedWindowingMode()) return; + + /** + * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation. + * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely + * on the TaskOrganizer side, so we just hand over the leash without doing any animation. + * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer + * needs to have flexibility to schedule that at an appropriate point in the animation. + */ + if (isControlledByTaskOrganizer()) return; if (toBounds == null /* toFullscreen */) { final Configuration parentConfig = getParent().getConfiguration(); final ActivityRecord top = topRunningNonOverlayTaskActivity(); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index fd871bdc1ba2..aa90248b97f8 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -83,6 +83,7 @@ import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -2079,7 +2080,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { ArrayList<ActivityRecord> readyToStopActivities = null; for (int i = mStoppingActivities.size() - 1; i >= 0; --i) { final ActivityRecord s = mStoppingActivities.get(i); - final boolean animating = s.isAnimating(TRANSITION); + final boolean animating = s.isAnimating(TRANSITION | PARENTS); if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible + " animating=" + animating + " finishing=" + s.finishing); @@ -2547,6 +2548,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityRecord::updatePictureInPictureMode, PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate); + task.getStack().setBounds(targetStackBounds); task.forAllActivities(c); c.recycle(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 474c5c960eb0..ded603c9fd77 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -226,6 +226,7 @@ import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowContainerTransaction; @@ -662,6 +663,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private FontScaleSettingObserver mFontScaleSettingObserver; + /** + * Stores the registration and state of TaskOrganizers in use. + */ + TaskOrganizerController mTaskOrganizerController = + new TaskOrganizerController(this, mGlobalLock); + private int mDeviceOwnerUid = Process.INVALID_UID; private final class FontScaleSettingObserver extends ContentObserver { @@ -1271,6 +1278,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .execute(); } + @Override + public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + enforceCallerIsRecentsOrHasPermission( + MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()"); + synchronized (mGlobalLock) { + mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode); + } + } @Override public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) { @@ -3319,6 +3334,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private void applyWindowContainerChange(ConfigurationContainer cc, + WindowContainerTransaction.Change c) { + sanitizeAndApplyConfigChange(cc, c); + + Rect enterPipBounds = c.getEnterPipBounds(); + if (enterPipBounds != null) { + Task tr = (Task) cc; + mStackSupervisor.updatePictureInPictureMode(tr, + enterPipBounds, true); + } + } + @Override public void applyContainerTransaction(WindowContainerTransaction t) { mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()"); @@ -3335,7 +3362,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { entries.next(); final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder( entry.getKey()).getContainer(); - sanitizeAndApplyConfigChange(cc, entry.getValue()); + applyWindowContainerChange(cc, entry.getValue()); } } } finally { @@ -4057,7 +4084,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { throw new IllegalArgumentException("Stack: " + stack + " doesn't support animated resize."); } - if (animate) { + /** + * TODO(b/146594635): Remove all PIP animation code from WM + * once SysUI handles animation. Don't even try to animate TaskOrganized tasks. + */ + if (animate && !stack.isControlledByTaskOrganizer()) { stack.animateResizePinnedStack(null /* destBounds */, null /* sourceHintBounds */, animationDuration, false /* fromFullscreen */); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 09111d07e8c1..014cb76c0064 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -436,10 +436,9 @@ public class AppTransition implements Dump { mNextAppTransition = TRANSIT_UNSET; mNextAppTransitionFlags = 0; setAppTransitionState(APP_STATE_RUNNING); - final AnimationAdapter topOpeningAnim = - (topOpeningApp != null && topOpeningApp.getAnimatingContainer() != null) - ? topOpeningApp.getAnimatingContainer().getAnimation() - : null; + final WindowContainer wc = + topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null; + final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null; int redoLayout = notifyAppTransitionStartingLocked(transit, topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index dd3365c900d7..d0310f1a7607 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -666,4 +666,8 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return sb.toString(); } } + + RemoteToken getRemoteToken() { + return mRemoteToken; + } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f9ad03f40723..9c62e9970b48 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2061,7 +2061,8 @@ public class DisplayPolicy { cf.set(displayFrames.mRestricted); } applyStableConstraints(sysUiFl, fl, cf, displayFrames); - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); @@ -2138,7 +2139,8 @@ public class DisplayPolicy { applyStableConstraints(sysUiFl, fl, cf, displayFrames); - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); @@ -2179,7 +2181,8 @@ public class DisplayPolicy { cf.set(displayFrames.mContent); df.set(displayFrames.mContent); } - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index c4b67d76607e..091f66c0b19a 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -279,6 +279,19 @@ final class InputMonitor { // we avoid reintroducing this concept by just choosing one of them here. inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left; + /** + * If the window is in a TaskManaged by a TaskOrganizer then most cropping + * will be applied using the SurfaceControl hierarchy from the Organizer. + * This means we need to make sure that these changes in crop are reflected + * in the input windows, and so ensure this flag is set so that + * the input crop always reflects the surface hierarchy. + * we may have some issues with modal-windows, but I guess we can + * cross that bridge when we come to implementing full-screen TaskOrg + */ + if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) { + inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */); + } + if (child.mGlobalScale != 1) { // If we are scaling the window, input coordinates need // to be inversely scaled to map from what is on screen diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index a13383d3991e..5a591ecd4746 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -129,6 +129,7 @@ class InsetsSourceProvider { if (win == null) { setServerVisible(false); mSource.setFrame(new Rect()); + mSource.setVisibleFrame(null); } else if (mControllable) { mWin.setControllableInsetProvider(this); if (mControlTarget != null) { @@ -160,6 +161,15 @@ class InsetsSourceProvider { mTmpRect.inset(mWin.mGivenContentInsets); } mSource.setFrame(mTmpRect); + + if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0 + || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) { + mTmpRect.set(mWin.getFrameLw()); + mTmpRect.inset(mWin.mGivenVisibleInsets); + mSource.setVisibleFrame(mTmpRect); + } else { + mSource.setVisibleFrame(null); + } } /** diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index a7bf6600d7b5..c3e815d10dda 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2168,12 +2168,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService.continueWindowLayout(); } + // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation. // Notify the pinned stack controller to prepare the PiP animation, expect callback - // delivered from SystemUI to WM to start the animation. - final PinnedStackController pinnedStackController = + // delivered from SystemUI to WM to start the animation. Unless we are using + // the TaskOrganizer in which case the animation will be entirely handled + // on that side. + if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED) + == null) { + final PinnedStackController pinnedStackController = display.mDisplayContent.getPinnedStackController(); - pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio, - null /* stackBounds */); + pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio, + null /* stackBounds */); + } // TODO: revisit the following statement after the animation is moved from WM to SysUI. // Update the visibility of all activities after the they have been reparented to the new diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9a140daad417..5cb7091bbed0 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -117,9 +117,11 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -130,6 +132,7 @@ import android.util.DisplayMetrics; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; @@ -425,6 +428,14 @@ class Task extends WindowContainer<WindowContainer> { } /** + * The TaskOrganizer which is delegated presentation of this task. If set the Task will + * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers + * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished. + */ + ITaskOrganizer mTaskOrganizer; + + + /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, * ActivityInfo, Intent, TaskDescription)} instead. */ @@ -445,6 +456,22 @@ class Task extends WindowContainer<WindowContainer> { _voiceSession, _voiceInteractor, stack); } + class TaskToken extends RemoteToken { + TaskToken(ConfigurationContainer container) { + super(container); + } + + @Override + public SurfaceControl getLeash() { + // We need to copy the SurfaceControl instead of returning the original + // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls + // to release themselves. + SurfaceControl sc = new SurfaceControl(); + sc.copyFrom(getSurfaceControl()); + return sc; + } + } + /** Don't use constructor directly. This is only used by XML parser. */ Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, @@ -469,7 +496,7 @@ class Task extends WindowContainer<WindowContainer> { mTaskDescription = _lastTaskDescription; // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED). setOrientation(SCREEN_ORIENTATION_UNSET); - mRemoteToken = new RemoteToken(this); + mRemoteToken = new TaskToken(this); affinityIntent = _affinityIntent; affinity = _affinity; rootAffinity = _rootAffinity; @@ -2179,6 +2206,10 @@ class Task extends WindowContainer<WindowContainer> { void removeImmediately() { if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId); EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask"); + + // If applicable let the TaskOrganizer know the Task is vanishing. + setTaskOrganizer(null); + super.removeImmediately(); } @@ -2567,6 +2598,12 @@ class Task extends WindowContainer<WindowContainer> { } boolean shouldAnimate() { + /** + * Animations are handled by the TaskOrganizer implementation. + */ + if (isControlledByTaskOrganizer()) { + return false; + } // Don't animate while the task runs recents animation but only if we are in the mode // where we cancel with deferred screenshot, which means that the controller has // transformed the task. @@ -3444,4 +3481,91 @@ class Task extends WindowContainer<WindowContainer> { XmlUtils.skipCurrentTag(in); } } + + boolean isControlledByTaskOrganizer() { + return mTaskOrganizer != null; + } + + @Override + protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) { + /** + * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the + * Surface in to its own hierarchy. + */ + if (isControlledByTaskOrganizer()) { + return; + } + super.reparentSurfaceControl(t, newParent); + } + + private void sendTaskAppeared() { + if (mSurfaceControl != null && mTaskOrganizer != null) { + mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this); + } + } + + private void sendTaskVanished() { + if (mTaskOrganizer != null) { + mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this); + } + } + + void setTaskOrganizer(ITaskOrganizer organizer) { + // Let the old organizer know it has lost control. + if (mTaskOrganizer != null) { + sendTaskVanished(); + } + mTaskOrganizer = organizer; + sendTaskAppeared(); + } + + // Called on Binder death. + void taskOrganizerDied() { + mTaskOrganizer = null; + } + + @Override + void setSurfaceControl(SurfaceControl sc) { + super.setSurfaceControl(sc); + // If the TaskOrganizer was set before we created the SurfaceControl, we need to + // emit the callbacks now. + sendTaskAppeared(); + } + + @Override + public void updateSurfacePosition() { + // Avoid fighting with the TaskOrganizer over Surface position. + if (isControlledByTaskOrganizer()) { + getPendingTransaction().setPosition(mSurfaceControl, 0, 0); + scheduleAnimation(); + return; + } else { + super.updateSurfacePosition(); + } + } + + @Override + void getRelativeDisplayedPosition(Point outPos) { + // In addition to updateSurfacePosition, we keep other code that sets + // position from fighting with the TaskOrganizer + if (isControlledByTaskOrganizer()) { + outPos.set(0, 0); + return; + } + super.getRelativeDisplayedPosition(outPos); + } + + @Override + public void setWindowingMode(int windowingMode) { + super.setWindowingMode(windowingMode); + windowingMode = getWindowingMode(); + /* + * Different windowing modes may be managed by different task organizers. If + * getTaskOrganizer returns null, we still call transferToTaskOrganizer to + * make sure we clear it. + */ + final ITaskOrganizer org = + mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + setTaskOrganizer(org); + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java new file mode 100644 index 000000000000..283be4010677 --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 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.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Stores the TaskOrganizers associated with a given windowing mode and + * their associated state. + */ +class TaskOrganizerController { + private static final String TAG = "TaskOrganizerController"; + + private WindowManagerGlobalLock mGlobalLock; + + private class DeathRecipient implements IBinder.DeathRecipient { + int mWindowingMode; + ITaskOrganizer mTaskOrganizer; + + DeathRecipient(ITaskOrganizer organizer, int windowingMode) { + mTaskOrganizer = organizer; + mWindowingMode = windowingMode; + } + + @Override + public void binderDied() { + synchronized (mGlobalLock) { + final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer); + for (int i = 0; i < state.mOrganizedTasks.size(); i++) { + state.mOrganizedTasks.get(i).taskOrganizerDied(); + } + mTaskOrganizerStates.remove(mTaskOrganizer); + if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) { + mTaskOrganizersForWindowingMode.remove(mWindowingMode); + } + } + } + }; + + class TaskOrganizerState { + ITaskOrganizer mOrganizer; + DeathRecipient mDeathRecipient; + + ArrayList<Task> mOrganizedTasks = new ArrayList<>(); + + void addTask(Task t) { + mOrganizedTasks.add(t); + } + + void removeTask(Task t) { + mOrganizedTasks.remove(t); + } + + TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) { + mOrganizer = organizer; + mDeathRecipient = deathRecipient; + } + }; + + + final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap(); + final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap(); + + final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap(); + + final ActivityTaskManagerService mService; + + TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) { + mService = atm; + mGlobalLock = lock; + } + + private void clearIfNeeded(int windowingMode) { + final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode); + if (oldState != null) { + oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0); + } + } + + /** + * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. + * If there was already a TaskOrganizer for this windowing mode it will be evicted + * and receive taskVanished callbacks in the process. + */ + void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + if (windowingMode != WINDOWING_MODE_PINNED) { + throw new UnsupportedOperationException( + "As of now only Pinned windowing mode is supported for registerTaskOrganizer"); + + } + clearIfNeeded(windowingMode); + DeathRecipient dr = new DeathRecipient(organizer, windowingMode); + try { + organizer.asBinder().linkToDeath(dr, 0); + } catch (RemoteException e) { + Slog.e(TAG, "TaskOrganizer failed to register death recipient"); + } + + final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); + mTaskOrganizersForWindowingMode.put(windowingMode, state); + + mTaskOrganizerStates.put(organizer, state); + } + + ITaskOrganizer getTaskOrganizer(int windowingMode) { + final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode); + if (state == null) { + return null; + } + return state.mOrganizer; + } + + private void sendTaskAppeared(ITaskOrganizer organizer, Task task) { + try { + organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskAppeared callback" + e); + } + } + + private void sendTaskVanished(ITaskOrganizer organizer, Task task) { + try { + organizer.taskVanished(task.getRemoteToken()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskVanished callback" + e); + } + } + + void onTaskAppeared(ITaskOrganizer organizer, Task task) { + TaskOrganizerState state = mTaskOrganizerStates.get(organizer); + + state.addTask(task); + sendTaskAppeared(organizer, task); + } + + void onTaskVanished(ITaskOrganizer organizer, Task task) { + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer); + sendTaskVanished(organizer, task); + + // This could trigger TaskAppeared for other tasks in the same stack so make sure + // we do this AFTER sending taskVanished. + state.removeTask(task); + } +} diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index cefef37a1363..f3880fa5dd09 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -342,7 +342,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mSurfaceControl == null) { // If we don't yet have a surface, but we now have a parent, we should // build a surface. - mSurfaceControl = makeSurface().build(); + setSurfaceControl(makeSurface().build()); getPendingTransaction().show(mSurfaceControl); updateSurfacePosition(); } else { @@ -496,7 +496,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mParent.getPendingTransaction().merge(getPendingTransaction()); } - mSurfaceControl = null; + setSurfaceControl(null); mLastSurfacePosition.set(0, 0); scheduleAnimation(); } @@ -777,7 +777,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * otherwise. */ boolean isWaitingForTransitionStart() { - return getActivity(app -> app.isWaitingForTransitionStart()) != null; + return false; } /** @@ -2209,4 +2209,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } return mParent.getDimmer(); } + + void setSurfaceControl(SurfaceControl sc) { + mSurfaceControl = sc; + } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 1ad6e86bae84..03969b01f3f9 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -13,6 +13,7 @@ cc_library_static { ], srcs: [ + ":graphicsstats_proto", "BroadcastRadio/JavaRef.cpp", "BroadcastRadio/NativeCallbackThread.cpp", "BroadcastRadio/BroadcastRadioService.cpp", @@ -103,6 +104,11 @@ cc_defaults { "libinputflinger", "libinputflinger_base", "libinputservice", + "libprotobuf-cpp-lite", + "libprotoutil", + "libstatspull", + "libstatssocket", + "libstatslog", "libschedulerservicehidl", "libsensorservice", "libsensorservicehidl", diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp index d1d253b94713..9353fbd71214 100644 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -23,6 +23,16 @@ #include <nativehelper/ScopedUtfChars.h> #include <JankTracker.h> #include <service/GraphicsStatsService.h> +#include <stats_pull_atom_callback.h> +#include <stats_event.h> +#include <statslog.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> +#include <android/util/ProtoOutputStream.h> +#include "android/graphics/Utils.h" +#include "core_jni_helpers.h" +#include "protos/graphicsstats.pb.h" +#include <cstring> +#include <memory> namespace android { @@ -77,6 +87,20 @@ static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { GraphicsStatsService::finishDump(dump); } +static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + std::vector<uint8_t>* result = new std::vector<uint8_t>(); + GraphicsStatsService::finishDumpInMemory(dump, + [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) { + std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2); + if (outBuffer->size() < totalSize) { + outBuffer->resize(totalSize); + } + std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize); + }, env, result); + return reinterpret_cast<jlong>(result); +} + static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { ScopedByteArrayRO buffer(env, jdata); @@ -93,19 +117,173 @@ static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpacka GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); } +static jobject gGraphicsStatsServiceObject = nullptr; +static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; + +static JNIEnv* getJNIEnv() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + JNIEnv* env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +using namespace google::protobuf; + +// Field ids taken from FrameTimingHistogram message in atoms.proto +#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 +#define FRAME_COUNTS_FIELD_NUMBER 2 + +static void writeCpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +static void writeGpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. +static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data, + const void* cookie) { + JNIEnv* env = getJNIEnv(); + if (!env) { + return false; + } + if (gGraphicsStatsServiceObject == nullptr) { + ALOGE("Failed to get graphicsstats service"); + return false; + } + + for (bool lastFullDay : {true, false}) { + jlong jdata = (jlong) env->CallLongMethod( + gGraphicsStatsServiceObject, + gGraphicsStatsService_pullGraphicsStatsMethodID, + (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + ALOGE("Failed to invoke graphicsstats service"); + return false; + } + if (!jdata) { + // null means data is not available for that day. + continue; + } + android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump; + std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata); + std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer); + int dataSize = buffer->size(); + if (!dataSize) { + // Data is not available for that day. + continue; + } + io::ArrayInputStream input{buffer->data(), dataSize}; + bool success = serviceDump.ParseFromZeroCopyStream(&input); + if (!success) { + ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", + serviceDump.InitializationErrorString().c_str(), dataSize); + return false; + } + + for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { + auto& stat = serviceDump.stats(stat_index); + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, android::util::GRAPHICS_STATS); + stats_event_write_string8(event, stat.package_name().c_str()); + stats_event_write_int64(event, (int64_t)stat.version_code()); + stats_event_write_int64(event, (int64_t)stat.stats_start()); + stats_event_write_int64(event, (int64_t)stat.stats_end()); + stats_event_write_int32(event, (int32_t)stat.pipeline()); + stats_event_write_int32(event, (int32_t)stat.summary().total_frames()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count()); + stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count()); + writeCpuHistogram(event, stat); + writeGpuHistogram(event, stat); + // TODO: fill in UI mainline module version, when the feature is available. + stats_event_write_int64(event, (int64_t)0); + stats_event_write_bool(event, !lastFullDay); + stats_event_build(event); + } + } + return true; +} + +// Register a puller for GRAPHICS_STATS atom with the statsd service. +static void nativeInit(JNIEnv* env, jobject javaObject) { + gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); + pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds + .timeout_ns = 2 * NS_PER_SEC, // 2 seconds + .additive_fields = nullptr, + .additive_fields_size = 0}; + register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback, + &metadata, nullptr); +} + +static void nativeDestructor(JNIEnv* env, jobject javaObject) { + //TODO: Unregister the puller callback when a new API is available. + env->DeleteGlobalRef(gGraphicsStatsServiceObject); + gGraphicsStatsServiceObject = nullptr; +} + static const JNINativeMethod sMethods[] = { { "nGetAshmemSize", "()I", (void*) getAshmemSize }, { "nCreateDump", "(IZ)J", (void*) createDump }, { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump }, { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump }, { "nFinishDump", "(J)V", (void*) finishDump }, + { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory }, { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer }, + { "nativeInit", "()V", (void*) nativeInit }, + { "nativeDestructor", "()V", (void*)nativeDestructor } }; int register_android_server_GraphicsStatsService(JNIEnv* env) { + jclass graphicsStatsService_class = FindClassOrDie(env, + "com/android/server/GraphicsStatsService"); + gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env, + graphicsStatsService_class, "pullGraphicsStats", "(Z)J"); return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService", sMethods, NELEM(sMethods)); } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp index 380ee942af98..cdbe77a3d64c 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -18,8 +18,3 @@ java_library_static { "compat-changeid-annotation-processor", ], } - -platform_compat_config { - name: "services-devicepolicy-platform-compat-config", - src: ":services.devicepolicy", -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2a08f5c2de12..d5ff2802499c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11723,6 +11723,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mStateCache; } + @Override + public List<String> getAllCrossProfilePackages() { + return DevicePolicyManagerService.this.getAllCrossProfilePackages(); + } + } private Intent createShowAdminSupportIntent(ComponentName admin, int userId) { diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java index 61cd88aac921..c93e5c5e4759 100644 --- a/services/net/java/android/net/ip/IpClientCallbacks.java +++ b/services/net/java/android/net/ip/IpClientCallbacks.java @@ -17,6 +17,7 @@ package android.net.ip; import android.net.DhcpResults; +import android.net.DhcpResultsParcelable; import android.net.Layer2PacketParcelable; import android.net.LinkProperties; @@ -69,6 +70,18 @@ public class IpClientCallbacks { public void onNewDhcpResults(DhcpResults dhcpResults) {} /** + * Callback called when new DHCP results are available. + * + * <p>This is purely advisory and not an indication of provisioning success or failure. This is + * only here for callers that want to expose DHCPv4 results to other APIs + * (e.g., WifiInfo#setInetAddress). + * + * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not + * the passed-in DhcpResults object is null. + */ + public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {} + + /** * Indicates that provisioning was successful. */ public void onProvisioningSuccess(LinkProperties newLp) {} diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 4d60e6239376..7f723b1c232b 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -119,6 +119,7 @@ public class IpClientUtil { @Override public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) { mCb.onNewDhcpResults(fromStableParcelable(dhcpResults)); + mCb.onNewDhcpResults(dhcpResults); } @Override diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index 8632ca4c2898..8b2f15c2babb 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -81,7 +81,10 @@ import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.List; /** @@ -1238,13 +1241,49 @@ public class UserBackupManagerServiceTest { assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2); } + /** + * Test that {@link UserBackupManagerService#dump()} for system user does not prefix dump with + * "User 0:". + */ + @Test + public void testDump_forSystemUser_DoesNotHaveUserPrefix() throws Exception { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + UserBackupManagerService service = + BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks( + UserHandle.USER_SYSTEM, + mContext, + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + + StringWriter dump = new StringWriter(); + service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]); + + assertThat(dump.toString()).startsWith("Backup Manager is "); + } + + /** + * Test that {@link UserBackupManagerService#dump()} for non-system user prefixes dump with + * "User <userid>:". + */ + @Test + public void testDump_forNonSystemUser_HasUserPrefix() throws Exception { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks(); + + StringWriter dump = new StringWriter(); + service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]); + + assertThat(dump.toString()).startsWith("User " + USER_ID + ":" + "Backup Manager is "); + } + private File createTestFile() throws IOException { File testFile = new File(mContext.getFilesDir(), "test"); testFile.createNewFile(); return testFile; } - /** * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method. diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 1829fb79699f..2fb2021de200 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -29,6 +29,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -56,7 +57,6 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiSsid; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -182,8 +182,8 @@ public class NetworkScoreServiceTest { } private ScanResult createScanResult(String ssid, String bssid) { - ScanResult result = new ScanResult(); - result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid); + ScanResult result = mock(ScanResult.class); + result.SSID = ssid; result.BSSID = bssid; return result; } @@ -794,7 +794,7 @@ public class NetworkScoreServiceTest { @Test public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception { List<ScanResult> invalidScanResults = Lists.newArrayList( - new ScanResult(), + mock(ScanResult.class), createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid), createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid), createScanResult(SSID, null), diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index 2326dfda7e12..d44476ed971d 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -27,6 +27,7 @@ import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -59,6 +60,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -83,6 +85,8 @@ public class BackupManagerServiceTest { @Mock private UserBackupManagerService mUserBackupManagerService; @Mock + private UserBackupManagerService mNonSystemUserBackupManagerService; + @Mock private Context mContextMock; @Mock private PrintWriter mPrintWriterMock; @@ -105,7 +109,7 @@ public class BackupManagerServiceTest { mUserServices = new SparseArray<>(); mUserServices.append(UserHandle.USER_SYSTEM, mUserBackupManagerService); - mUserServices.append(NON_USER_SYSTEM, mUserBackupManagerService); + mUserServices.append(NON_USER_SYSTEM, mNonSystemUserBackupManagerService); when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock); when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock); @@ -512,6 +516,26 @@ public class BackupManagerServiceTest { mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]); verifyNoMoreInteractions(mUserBackupManagerService); + verifyNoMoreInteractions(mNonSystemUserBackupManagerService); + } + + /** + * Test that {@link BackupManagerService#dump()} dumps system user information before non-system + * user information. + */ + + @Test + public void testDump_systemUserFirst() { + String[] args = new String[0]; + mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args); + + InOrder inOrder = + inOrder(mUserBackupManagerService, mNonSystemUserBackupManagerService); + inOrder.verify(mUserBackupManagerService) + .dump(mFileDescriptorStub, mPrintWriterMock, args); + inOrder.verify(mNonSystemUserBackupManagerService) + .dump(mFileDescriptorStub, mPrintWriterMock, args); + inOrder.verifyNoMoreInteractions(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java new file mode 100644 index 000000000000..1eb5eb51504a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java @@ -0,0 +1,142 @@ +/* + * 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.integrity.model; + +import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; +import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; +import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.utils.TestUtils.getBits; +import static com.android.server.integrity.utils.TestUtils.getBytes; +import static com.android.server.integrity.utils.TestUtils.getValueBits; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.content.integrity.AtomicFormula; +import android.content.integrity.CompoundFormula; +import android.content.integrity.Rule; + +import com.android.server.integrity.parser.BinaryFileOperations; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; + +@RunWith(JUnit4.class) +public class BitTrackedInputStreamTest { + private static final String COMPOUND_FORMULA_START_BITS = + getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); + private static final String COMPOUND_FORMULA_END_BITS = + getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); + private static final String ATOMIC_FORMULA_START_BITS = + getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); + private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); + private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); + private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); + private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); + + private static final String IS_NOT_HASHED = "0"; + private static final String START_BIT = "1"; + private static final String END_BIT = "1"; + + @Test + public void testBitOperationsCountBitsCorrectly() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + + // Right after construction, the read bits count should be 0. + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); + + // Get next 10 bits should result with 10 bits read. + bitTrackedInputStream.getNext(10); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10); + + // When we move the cursor 8 bytes, we should point to 64 bits. + bitTrackedInputStream.setCursorToByteLocation(8); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64); + + // Read until the end and the total size of the input stream should be available. + while (bitTrackedInputStream.hasNext()) { + bitTrackedInputStream.getNext(1); + } + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128); + } + + @Test + public void testBitInputStreamOperationsStillWork() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); + + // Read until the string parameter. + String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream); + + // Verify that the read bytes are counted. + assertThat(stringValue).isEqualTo(packageName); + assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0); + } + + @Test + public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + + // Read more than two bytes. + bitTrackedInputStream.getNext(20); + + // Ask to move the cursor to the second byte. + assertThrows( + IllegalStateException.class, + () -> bitTrackedInputStream.setCursorToByteLocation(2)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java index 5ecb8b5c8169..c7cc343dd77e 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java @@ -14,12 +14,10 @@ * limitations under the License. */ -package com.android.server.integrity.serializer; +package com.android.server.integrity.model; import static com.google.common.truth.Truth.assertThat; -import com.android.server.integrity.model.BitOutputStream; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 9cc0ed85a044..51f5c755754c 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -48,6 +48,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -97,8 +98,10 @@ public class RuleBinaryParserTest { private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); + private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList(); + @Test - public void testBinaryStream_validCompoundFormula() throws Exception { + public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -131,13 +134,13 @@ public class RuleBinaryParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = binaryParser.parse(inputStream); + List<Rule> rules = binaryParser.parse(inputStream, NO_INDEXING); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @Test - public void testBinaryString_validCompoundFormula_notConnector() throws Exception { + public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -175,7 +178,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validCompoundFormula_andConnector() throws Exception { + public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -223,7 +226,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validCompoundFormula_orConnector() throws Exception { + public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -272,7 +275,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_stringValue() throws Exception { + public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -304,7 +307,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_hashedValue() throws Exception { + public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception { String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; String ruleBits = START_BIT @@ -337,7 +340,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_integerValue() throws Exception { + public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception { int versionCode = 1; String ruleBits = START_BIT @@ -365,7 +368,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_booleanValue() throws Exception { + public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception { String isPreInstalled = "1"; String ruleBits = START_BIT @@ -392,7 +395,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidAtomicFormula() throws Exception { + public void testBinaryString_invalidAtomicFormula_noIndexing() { int versionCode = 1; String ruleBits = START_BIT @@ -415,7 +418,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_withNoRuleList() throws RuleParseException { + public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException { ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length); rule.put(DEFAULT_FORMAT_VERSION_BYTES); RuleParser binaryParser = new RuleBinaryParser(); @@ -426,7 +429,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_withEmptyRule() throws RuleParseException { + public void testBinaryString_withEmptyRule_noIndexing() { String ruleBits = START_BIT; byte[] ruleBytes = getBytes(ruleBits); ByteBuffer rule = @@ -442,7 +445,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception { + public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -478,7 +481,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidOperator() throws Exception { + public void testBinaryString_invalidRule_invalidOperator_noIndexing() { int versionCode = 1; String ruleBits = START_BIT @@ -506,7 +509,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidEffect() throws Exception { + public void testBinaryString_invalidRule_invalidEffect_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -536,7 +539,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidConnector() throws Exception { + public void testBinaryString_invalidRule_invalidConnector_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -566,7 +569,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidKey() throws Exception { + public void testBinaryString_invalidRule_invalidKey_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -596,7 +599,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidSeparator() throws Exception { + public void testBinaryString_invalidRule_invalidSeparator_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -626,7 +629,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidEndMarker() throws Exception { + public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -653,4 +656,65 @@ public class RuleBinaryParserTest { /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit", () -> binaryParser.parse(rule.array())); } + + @Test + public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly() + throws Exception { + String packageName2 = "com.test.2"; + + byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1")); + byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2)); + byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3")); + + ByteBuffer rule = + ByteBuffer.allocate( + DEFAULT_FORMAT_VERSION_BYTES.length + + ruleBytes1.length + + ruleBytes2.length + + ruleBytes3.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes1); + rule.put(ruleBytes2); + rule.put(ruleBytes3); + InputStream inputStream = new ByteArrayInputStream(rule.array()); + + RuleParser binaryParser = new RuleBinaryParser(); + + List<RuleIndexRange> indexRanges = new ArrayList<>(); + indexRanges.add( + new RuleIndexRange( + DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length, + DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length + + ruleBytes2.length)); + List<Rule> rules = binaryParser.parse(inputStream, indexRanges); + + Rule expectedRule = + new Rule( + new CompoundFormula( + CompoundFormula.NOT, + Collections.singletonList( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName2, + /* isHashedValue= */ false))), + Rule.DENY); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + private static String getRulesWithPackageName(String packageName) { + return START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + + } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java index a14197b17529..6944aee7fcb9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java @@ -73,7 +73,7 @@ public class RuleXmlParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = xmlParser.parse(inputStream); + List<Rule> rules = xmlParser.parse(inputStream, Collections.emptyList()); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @@ -623,7 +623,7 @@ public class RuleXmlParserTest { assertExpectException( RuleParseException.class, /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag", - () -> xmlParser.parse(inputStream)); + () -> xmlParser.parse(inputStream, Collections.emptyList())); } private String generateTagWithAttribute( diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index 821d97acc230..670bd8107bed 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -67,13 +67,25 @@ import javax.crypto.KeyGenerator; public class PlatformKeyManagerTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; - private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; + private static final int MIN_GENERATION_ID = 1000000; + private static final int PRIMARY_USER_ID_FIXTURE = 0; private static final int USER_ID_FIXTURE = 42; private static final long USER_SID = 4200L; private static final String KEY_ALGORITHM = "AES"; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias"; + private static final String ENCRYPTION_KEY_ALIAS_1 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt"; + private static final String DECRYPTION_KEY_ALIAS_1 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt"; + private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 = + "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt"; + private static final String ENCRYPTION_KEY_ALIAS_2 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt"; + private static final String DECRYPTION_KEY_ALIAS_2 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt"; + @Mock private Context mContext; @Mock private KeyStoreProxy mKeyStoreProxy; @Mock private KeyguardManager mKeyguardManager; @@ -114,7 +126,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any(), any()); } @@ -156,7 +168,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -187,19 +199,33 @@ public class PlatformKeyManagerTest { } @Test - public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception { + public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception { + mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE); + + assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired()); + } + + @Test + public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception { + mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE); + + assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired()); + } + + @Test + public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired() + throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired()); + assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired()); } @Test - public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception { + public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired() + throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals( - USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS, - getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds()); + assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired()); } @Test @@ -219,7 +245,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy, never()).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -231,7 +257,7 @@ public class PlatformKeyManagerTest { expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE)); verify(mKeyStoreProxy, never()).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -251,15 +277,15 @@ public class PlatformKeyManagerTest { public void init_savesGenerationIdToDatabase() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, + assertEquals(MIN_GENERATION_ID, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE)); } @Test - public void init_setsGenerationIdTo1() throws Exception { + public void init_setsGenerationId() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -268,22 +294,20 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -294,226 +318,194 @@ public class PlatformKeyManagerTest { @Test public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any())).thenReturn(generateAndroidKeyStoreKey()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); } @@ -523,7 +515,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); - assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -533,17 +525,17 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); mPlatformKeyManager.regenerate(USER_ID_FIXTURE); // Removes second generation keys. verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_2)); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt")); + eq(DECRYPTION_KEY_ALIAS_2)); } @Test @@ -553,7 +545,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any(), any()); } @@ -565,14 +557,14 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any(), any()); } private KeyProtection getEncryptKeyProtection() throws Exception { verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any(), mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); @@ -580,7 +572,15 @@ public class PlatformKeyManagerTest { private KeyProtection getDecryptKeyProtection() throws Exception { verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), + any(), + mProtectionParameterCaptor.capture()); + return (KeyProtection) mProtectionParameterCaptor.getValue(); + } + + private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception { + verify(mKeyStoreProxy).setEntry( + eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1), any(), mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 68900175cc8f..ac7447006444 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; 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.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; @@ -84,7 +85,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Map; import java.util.Random; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -157,7 +158,7 @@ public class RecoverableKeyStoreManagerTest { @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private ApplicationKeyStorage mApplicationKeyStorage; @Mock private CleanupManager mCleanupManager; - @Mock private ExecutorService mExecutorService; + @Mock private ScheduledExecutorService mExecutorService; @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; @@ -1253,7 +1254,7 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager.lockScreenSecretAvailable( LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11); - verify(mExecutorService).execute(any()); + verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any()); } @Test @@ -1263,7 +1264,7 @@ public class RecoverableKeyStoreManagerTest { "password".getBytes(), 11); - verify(mExecutorService).execute(any()); + verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any()); } private static byte[] encryptedApplicationKey( diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index c478ec472e61..15327b6e5463 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -156,8 +156,7 @@ public class PackageInstallerSessionTest { if (isMultiPackage) { params.isMultiPackage = true; } - InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller", - false); + InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller"); return new PackageInstallerSession( /* callback */ null, /* context */null, diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java index a83d94001cf8..f871203728c0 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java @@ -98,7 +98,7 @@ public class AppDataRollbackHelperTest { final int[] installedUsers) { return new PackageRollbackInfo( new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1), - new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers), + new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers), new SparseLongArray()); } diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java index 757a884f8ded..d0d2edc59861 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -87,13 +87,15 @@ public class RollbackStoreTest { + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55}," + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':" + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'}," - + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':" + + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false," + + "'installedUsers':" + "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6}," + "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546," + "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips'," + "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test'," + "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18," - + "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79]," + + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false," + + "'installedUsers':[55,79]," + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello'," + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}]," + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z'," @@ -155,7 +157,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("com.made.up", 18), new VersionedPackage("com.something.else", 5), new IntArray(), - new ArrayList<>(), false, new IntArray(), new SparseLongArray()); + new ArrayList<>(), false, false, new IntArray(), new SparseLongArray()); pkgInfo1.getPendingBackups().add(8); pkgInfo1.getPendingBackups().add(888); pkgInfo1.getPendingBackups().add(88885); @@ -175,7 +177,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo( new VersionedPackage("another.package", 2), new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo2.getPendingBackups().add(57); pkgInfo2.getPendingRestores().add( @@ -205,7 +207,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55), new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo1.getPendingBackups().add(59); pkgInfo1.getPendingBackups().add(1245); pkgInfo1.getPendingBackups().add(124544); @@ -224,7 +226,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28), new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo2.getPendingBackups().add(5); pkgInfo2.getPendingRestores().add( diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index e368d634b968..164c88382828 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -295,7 +295,8 @@ public class RollbackUnitTest { String packageName, long fromVersion, long toVersion, boolean isApex) { return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion), new VersionedPackage(packageName, toVersion), - new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray()); + new IntArray(), new ArrayList<>(), isApex, false, new IntArray(), + new SparseLongArray()); } private static class PackageRollbackInfoForPackage implements diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index f8915c06b555..0f75816082fd 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -155,7 +155,16 @@ public class SoundTriggerMiddlewareImplTest { return properties; } - private static void validateDefaultProperties(SoundTriggerModuleProperties properties, + private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3( + boolean supportConcurrentCapture) { + android.hardware.soundtrigger.V2_3.Properties properties = + new android.hardware.soundtrigger.V2_3.Properties(); + properties.base = createDefaultProperties(supportConcurrentCapture); + properties.supportedModelArch = "supportedModelArch"; + return properties; + } + + private void validateDefaultProperties(SoundTriggerModuleProperties properties, boolean supportConcurrentCapture) { assertEquals("implementor", properties.implementor); assertEquals("description", properties.description); @@ -173,8 +182,20 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(supportConcurrentCapture, properties.concurrentCapture); assertTrue(properties.triggerInEvent); assertEquals(432, properties.powerConsumptionMw); + + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + assertEquals("supportedModelArch", properties.supportedModelArch); + } else { + assertEquals("", properties.supportedModelArch); + } } + private void verifyNotGetProperties() throws RemoteException { + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver, + never()).getProperties(any()); + } + } private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0( int hwHandle, @@ -290,6 +311,22 @@ public class SoundTriggerMiddlewareImplTest { properties); return null; }).when(mHalDriver).getProperties(any()); + + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver = + (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver; + doAnswer(invocation -> { + android.hardware.soundtrigger.V2_3.Properties properties = + createDefaultProperties_2_3( + supportConcurrentCapture); + ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback) + invocation.getArgument( + 0)).onValues(0, + properties); + return null; + }).when(driver).getProperties_2_3(any()); + } + mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider); } @@ -716,6 +753,7 @@ public class SoundTriggerMiddlewareImplTest { SoundTriggerModuleProperties properties = allDescriptors[0].properties; validateDefaultProperties(properties, true); + verifyNotGetProperties(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 12ba219d0365..1dd7e64690c7 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -365,8 +365,9 @@ public class AppStandbyControllerTests { public void testSetAppStandbyBucket() throws Exception { // For a known package, standby bucket should be set properly reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); // For an unknown package, standby bucket should not be set, hence NEVER is returned @@ -374,7 +375,7 @@ public class AppStandbyControllerTests { mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID); isPackageInstalled = false; // Mock package is not installed mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); isPackageInstalled = true; // Reset mocked variable for other tests assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN)); } @@ -468,12 +469,13 @@ public class AppStandbyControllerTests { @Test public void testPredictionTimedout() throws Exception { // Set it to timeout or usage, so that prediction can override it + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); // Fast forward 12 hours @@ -497,29 +499,31 @@ public class AppStandbyControllerTests { @Test public void testOverrides() throws Exception { // Can force to NEVER + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't override FORCED reason mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_MAIN_PREDICTED, 1 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't override NEVER + mInjector.mElapsedRealtime = 2 * HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_DEFAULT, 2 * HOUR_MS); + REASON_MAIN_DEFAULT); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't set to NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_USAGE, 2 * HOUR_MS); + REASON_MAIN_USAGE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); } @@ -530,7 +534,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // bucketing works after timeout @@ -554,15 +558,17 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_MAIN_PREDICTED, 1000); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); + mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_WORKING_SET); + mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_FREQUENT); } @@ -582,18 +588,18 @@ public class AppStandbyControllerTests { // Still in ACTIVE after first USER_INTERACTION times out mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // Both timed out, so NOTIFICATION_SEEN timeout should be effective mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_WORKING_SET); mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_RARE); } @@ -625,7 +631,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; // Make sure app is in NEVER bucket mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED, mInjector.mElapsedRealtime); + REASON_MAIN_FORCED); mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_NEVER); @@ -670,7 +676,7 @@ public class AppStandbyControllerTests { // Predict to ACTIVE mInjector.mElapsedRealtime += 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // CheckIdleStates should not change the prediction @@ -687,7 +693,7 @@ public class AppStandbyControllerTests { // Predict to FREQUENT mInjector.mElapsedRealtime = RARE_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_FREQUENT); // Add a short timeout event diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index eb45960bc82f..bd7d9ecf84d1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -20,7 +20,6 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static org.mockito.Matchers.any; @@ -65,11 +64,10 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { NotificationChannel updatedChannel = new NotificationChannel("a", "", IMPORTANCE_HIGH); - when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false))) + when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false))) .thenReturn(updatedChannel); assertNull(extractor.process(r)); assertEquals(updatedChannel, r.getChannel()); } - } 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 172df99921a0..62f52306ea49 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -112,6 +112,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -133,6 +134,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.TestablePermissions; import android.testing.TestableResources; import android.text.Html; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -475,7 +477,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled); when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled); when(mPreferencesHelper.getNotificationChannel( - anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn( mTestNotificationChannel); when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( mTestNotificationChannel.getImportance()); @@ -1762,7 +1764,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); verify(mPreferencesHelper, times(1)).getNotificationChannel( - anyString(), anyInt(), eq("foo"), anyBoolean()); + anyString(), anyInt(), eq("foo"), eq(null), anyBoolean()); } @Test @@ -1777,7 +1779,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); verify(mPreferencesHelper, times(1)).getNotificationChannel( - anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean()); + anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null), + anyBoolean()); } @Test @@ -3210,9 +3213,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {nv}, new NotificationVisibility[]{}); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {}, new NotificationVisibility[]{nv}); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); } @@ -4463,6 +4468,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testOnPanelRevealedAndHidden() { + int items = 5; + mService.mNotificationDelegate.onPanelRevealed(false, items); + verify(mAssistants, times(1)).onPanelRevealed(eq(items)); + + mService.mNotificationDelegate.onPanelHidden(); + verify(mAssistants, times(1)).onPanelHidden(); + } + + @Test public void testOnNotificationSmartReplySent() { final int replyIndex = 2; final String reply = "Hello"; @@ -5044,10 +5059,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.build(), new UserHandle(mUid), null, 0); // Make sure it has foreground service sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + sbn.getId(), sbn.getNotification(), sbn.getUserId()); waitForIdle(); // yes phone call, yes person, yes foreground service, but not allowed, no bubble @@ -5802,4 +5816,79 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mHistoryManager, times(1)).addNotification(any()); } + + @Test + public void createConversationNotificationChannel() throws Exception { + NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + original.setAllowBubbles(!original.canBubble()); + original.setShowBadge(!original.canShowBadge()); + + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel); + assertEquals(original, orig); + assertFalse(TextUtils.isEmpty(orig.getName())); + + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList( + orig))); + + mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend"); + + NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, original.getId(), "friend"); + + assertEquals(original.getName(), friendChannel.getName()); + assertEquals(original.getId(), friendChannel.getParentChannelId()); + assertEquals("friend", friendChannel.getConversationId()); + assertEquals(null, original.getConversationId()); + assertEquals(original.canShowBadge(), friendChannel.canShowBadge()); + assertEquals(original.canBubble(), friendChannel.canBubble()); + assertFalse(original.getId().equals(friendChannel.getId())); + assertNotNull(friendChannel.getId()); + } + + @Test + public void deleteConversationNotificationChannels() throws Exception { + NotificationChannel messagesParent = + new NotificationChannel("messages", "messages", IMPORTANCE_HIGH); + Parcel msgParcel = Parcel.obtain(); + messagesParent.writeToParcel(msgParcel, 0); + msgParcel.setDataPosition(0); + + NotificationChannel callsParent = + new NotificationChannel("calls", "calls", IMPORTANCE_HIGH); + Parcel callParcel = Parcel.obtain(); + callsParent.writeToParcel(callParcel, 0); + callParcel.setDataPosition(0); + + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList( + messagesParent, callsParent))); + + String conversationId = "friend"; + + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId); + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel), + conversationId); + + NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, messagesParent.getId(), conversationId); + NotificationChannel callsChild = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, callsParent.getId(), conversationId); + + assertEquals(messagesParent.getId(), messagesChild.getParentChannelId()); + assertEquals(conversationId, messagesChild.getConversationId()); + + assertEquals(callsParent.getId(), callsChild.getParentChannelId()); + assertEquals(conversationId, callsChild.getConversationId()); + + mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId); + + assertNull(mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, messagesParent.getId(), conversationId)); + assertNull(mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, callsParent.getId(), conversationId)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 8961796ed617..7173e0cb625c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -15,6 +15,7 @@ */ package com.android.server.notification; +import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; @@ -224,6 +225,26 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(expected.getGroup(), actual.getGroup()); assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes()); assertEquals(expected.getLightColor(), actual.getLightColor()); + assertEquals(expected.getParentChannelId(), actual.getParentChannelId()); + assertEquals(expected.getConversationId(), actual.getConversationId()); + } + + private void compareChannelsParentChild(NotificationChannel parent, + NotificationChannel actual, String conversationId) { + assertEquals(parent.getName(), actual.getName()); + assertEquals(parent.getDescription(), actual.getDescription()); + assertEquals(parent.shouldVibrate(), actual.shouldVibrate()); + assertEquals(parent.shouldShowLights(), actual.shouldShowLights()); + assertEquals(parent.getImportance(), actual.getImportance()); + assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility()); + assertEquals(parent.getSound(), actual.getSound()); + assertEquals(parent.canBypassDnd(), actual.canBypassDnd()); + assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern())); + assertEquals(parent.getGroup(), actual.getGroup()); + assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes()); + assertEquals(parent.getLightColor(), actual.getLightColor()); + assertEquals(parent.getId(), actual.getParentChannelId()); + assertEquals(conversationId, actual.getConversationId()); } private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) { @@ -2786,4 +2807,40 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(user10Importance, mHelper.getNotificationChannel( pkg, uidList10[0], channelId, false).getImportance()); } + + @Test + public void testGetConversationNotificationChannel() { + String conversationId = "friend"; + + NotificationChannel parent = + new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + + NotificationChannel friend = new NotificationChannel(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId), + "messages", IMPORTANCE_DEFAULT); + friend.setConversationId(parent.getId(), conversationId); + mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + + compareChannelsParentChild(parent, mHelper.getNotificationChannel( + PKG_O, UID_O, parent.getId(), conversationId, false), conversationId); + } + + @Test + public void testConversationNotificationChannelsRequireParents() { + String parentId = "does not exist"; + String conversationId = "friend"; + + NotificationChannel friend = new NotificationChannel(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId), + "messages", IMPORTANCE_DEFAULT); + friend.setConversationId(parentId, conversationId); + + try { + mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + fail("allowed creation of conversation channel without a parent"); + } catch (IllegalArgumentException e) { + // good + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 09ac9ce381c0..d819b1ada659 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -63,6 +63,26 @@ public class InsetsSourceProviderTest extends WindowTestsBase { assertEquals(Insets.of(0, 100, 0, 0), mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), false /* ignoreVisibility */)); + assertEquals(Insets.of(0, 100, 0, 0), + mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500))); + } + + @Test + public void testPostLayout_givenInsets() { + final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + ime.getFrameLw().set(0, 0, 500, 100); + ime.getGivenContentInsetsLw().set(0, 0, 0, 60); + ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75); + ime.mHasSurface = true; + mProvider.setWindow(ime, null); + mProvider.onPostLayout(); + assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame()); + assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame()); + assertEquals(Insets.of(0, 40, 0, 0), + mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */)); + assertEquals(Insets.of(0, 25, 0, 0), + mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500))); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java new file mode 100644 index 000000000000..8d2da1e6cb5b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.os.Binder; +import android.os.RemoteException; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskOrganizer}. + * + * Build/Install/Run: + * atest WmTests:TaskOrganizerTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class TaskOrganizerTests extends WindowTestsBase { + private ITaskOrganizer makeAndRegisterMockOrganizer() { + final ITaskOrganizer organizer = mock(ITaskOrganizer.class); + when(organizer.asBinder()).thenReturn(new Binder()); + + mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED); + + return organizer; + } + + @Test + public void testAppearVanish() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.removeImmediately(); + verify(organizer).taskVanished(any()); + } + + @Test + public void testSwapOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + task.setTaskOrganizer(organizer2); + verify(organizer).taskVanished(any()); + verify(organizer2).taskAppeared(any(), any()); + } + + @Test + public void testClearOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.setTaskOrganizer(null); + verify(organizer).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + } + + @Test + public void testTransferStackToOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final Task task2 = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + stack.transferToTaskOrganizer(organizer); + + verify(organizer, times(2)).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + assertTrue(task2.isControlledByTaskOrganizer()); + + stack.transferToTaskOrganizer(null); + + verify(organizer, times(2)).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + assertFalse(task2.isControlledByTaskOrganizer()); + } + + @Test + public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException { + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + task.setWindowingMode(WINDOWING_MODE_PINNED); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + verify(organizer).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + } + + @Test + public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException { + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final Task task2 = createTaskInStack(stack, 0 /* userId */); + stack.setWindowingMode(WINDOWING_MODE_PINNED); + verify(organizer, times(2)).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + assertTrue(task2.isControlledByTaskOrganizer()); + + stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + verify(organizer, times(2)).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + assertFalse(task2.isControlledByTaskOrganizer()); + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index c900f386b438..8397aa485595 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1568,44 +1568,16 @@ public class UsageStatsService extends SystemService implements } @Override - public void setAppStandbyBucket(String packageName, - int bucket, int userId) { + public void setAppStandbyBucket(String packageName, int bucket, int userId) { getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE, "No permission to change app standby state"); - if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE - || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) { - throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket); - } final int callingUid = Binder.getCallingUid(); - try { - userId = ActivityManager.getService().handleIncomingUser( - Binder.getCallingPid(), callingUid, userId, false, true, - "setAppStandbyBucket", null); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final boolean systemCaller = UserHandle.isCore(callingUid); - final int reason = systemCaller - ? UsageStatsManager.REASON_MAIN_FORCED - : UsageStatsManager.REASON_MAIN_PREDICTED; + final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { - final int packageUid = mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId); - // Caller cannot set their own standby state - if (packageUid == callingUid) { - throw new IllegalArgumentException("Cannot set your own standby bucket"); - } - if (packageUid < 0) { - throw new IllegalArgumentException( - "Cannot set standby bucket for non existent package (" + packageName - + ")"); - } - mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason, - SystemClock.elapsedRealtime(), shellCaller); + mAppStandby.setAppStandbyBucket(packageName, bucket, userId, + callingUid, callingPid); } finally { Binder.restoreCallingIdentity(token); } @@ -1643,37 +1615,11 @@ public class UsageStatsService extends SystemService implements "No permission to change app standby state"); final int callingUid = Binder.getCallingUid(); - try { - userId = ActivityManager.getService().handleIncomingUser( - Binder.getCallingPid(), callingUid, userId, false, true, - "setAppStandbyBucket", null); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final int reason = shellCaller - ? UsageStatsManager.REASON_MAIN_FORCED - : UsageStatsManager.REASON_MAIN_PREDICTED; + final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - List<AppStandbyInfo> bucketList = appBuckets.getList(); - for (AppStandbyInfo bucketInfo : bucketList) { - final String packageName = bucketInfo.mPackageName; - final int bucket = bucketInfo.mStandbyBucket; - if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE - || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) { - throw new IllegalArgumentException( - "Cannot set the standby bucket to " + bucket); - } - // Caller cannot set their own standby state - if (mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_ANY_USER, userId) == callingUid) { - throw new IllegalArgumentException("Cannot set your own standby bucket"); - } - mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason, - elapsedRealtime, shellCaller); - } + mAppStandby.setAppStandbyBuckets(appBuckets.getList(), userId, + callingUid, callingPid); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 198b4c31249a..bde2cfd52c0f 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -611,63 +611,89 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { int setParameter(UUID modelId, @ModelParams int modelParam, int value) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_set_parameter", 1); - if (modelId == null || mModule == null) { - return SoundTrigger.STATUS_ERROR; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "SetParameter: Invalid model id:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } + return setParameterLocked(mModelDataMap.get(modelId), modelParam, value); + } + } - return mModule.setParameter(modelData.getHandle(), modelParam, value); + int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) { + synchronized (mLock) { + return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value); } } - int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam, + int value) { + MetricsLogger.count(mContext, "sth_set_parameter", 1); + if (mModule == null) { + return SoundTrigger.STATUS_NO_INIT; + } + if (modelData == null || !modelData.isModelLoaded()) { + Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData); + return SoundTrigger.STATUS_BAD_VALUE; + } + + return mModule.setParameter(modelData.getHandle(), modelParam, value); + } + + int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_get_parameter", 1); - if (mModule == null) { - throw new UnsupportedOperationException("SoundTriggerModule not initialized"); - } + return getParameterLocked(mModelDataMap.get(modelId), modelParam); + } + } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - throw new IllegalArgumentException("Invalid model id:" + modelId); - } - if (!modelData.isModelLoaded()) { - throw new UnsupportedOperationException("Given model is not loaded:" + modelId); - } + int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } - return mModule.getParameter(modelData.getHandle(), modelParam); + private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_get_parameter", 1); + if (mModule == null) { + throw new UnsupportedOperationException("SoundTriggerModule not initialized"); } + + if (modelData == null) { + throw new IllegalArgumentException("Invalid model id"); + } + if (!modelData.isModelLoaded()) { + throw new UnsupportedOperationException("Given model is not loaded:" + modelData); + } + + return mModule.getParameter(modelData.getHandle(), modelParam); } @Nullable ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_query_parameter", 1); - if (mModule == null) { - return null; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "queryParameter: Invalid model id:" + modelId); - return null; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId); - return null; - } + return queryParameterLocked(mModelDataMap.get(modelId), modelParam); + } + } - return mModule.queryParameter(modelData.getHandle(), modelParam); + @Nullable + ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@Nullable ModelData modelData, + @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_query_parameter", 1); + if (mModule == null) { + return null; + } + if (modelData == null) { + Slog.w(TAG, "queryParameter: Invalid model id"); + return null; } + if (!modelData.isModelLoaded()) { + Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData); + return null; + } + + return mModule.queryParameter(modelData.getHandle(), modelParam); } //---- SoundTrigger.StatusListener methods diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java index d05e044499ab..54dffdc4c13a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java @@ -16,17 +16,17 @@ package com.android.server.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.Keyphrase; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; -import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; -import android.hardware.soundtrigger.SoundTriggerModule; + +import com.android.server.voiceinteraction.VoiceInteractionManagerService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -71,6 +71,58 @@ public abstract class SoundTriggerInternal { public abstract ModuleProperties getModuleProperties(); /** + * Set a model specific {@link ModelParams} with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value); + + /** + * Get a model specific {@link ModelParams}. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See ModelParams for parameter default values. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return value of parameter + * @throws UnsupportedOperationException if hal or model do not support this API. + * queryParameter should be checked first. + * @throws IllegalArgumentException if invalid model handle or parameter is passed. + * queryParameter should be checked first. + */ + public abstract int getParameter(int keyphraseId, @ModelParams int modelParam); + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter} + * or {@link SoundTriggerInternal#getParameter}. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public abstract ModelParamRange queryParameter(int keyphraseId, + @ModelParams int modelParam); + + /** * Unloads (and stops if running) the given keyphraseId */ public abstract int unloadKeyphraseModel(int keyphaseId); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 68b16f39e149..e37755bddcaa 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1446,26 +1446,45 @@ public class SoundTriggerService extends SystemService { @Override public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener, recognitionConfig); } @Override public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener); } @Override public ModuleProperties getModuleProperties() { - if (!isInitialized()) return null; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.getModuleProperties(); } @Override + public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value); + } + + @Override + public int getParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam); + } + + @Override + @Nullable + public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam); + } + + @Override public int unloadKeyphraseModel(int keyphraseId) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index e9db31ba3e25..06c807421d1a 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -41,7 +41,9 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.os.Binder; @@ -1084,6 +1086,55 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public int setParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam, int value) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + public int getParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.getParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + @Nullable + public ModelParamRange queryParameter(IVoiceInteractionService service, + int keyphraseId, @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + private synchronized void unloadAllKeyphraseModels() { for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) { final long caller = Binder.clearCallingIdentity(); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 9cf4803966c6..c4c1e21e7c41 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -49,6 +49,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -882,7 +883,8 @@ public class TelecomManager { */ public TelecomManager(Context context, ITelecomService telecomServiceImpl) { Context appContext = context.getApplicationContext(); - if (appContext != null) { + if (appContext != null && Objects.equals(context.getFeatureId(), + appContext.getFeatureId())) { mContext = appContext; } else { mContext = context; @@ -916,7 +918,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e); @@ -1113,7 +1115,8 @@ public class TelecomManager { public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() { try { if (isServiceConnected()) { - return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName()); + return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e); @@ -1138,8 +1141,8 @@ public class TelecomManager { boolean includeDisabledAccounts) { try { if (isServiceConnected()) { - return getTelecomService().getCallCapablePhoneAccounts( - includeDisabledAccounts, mContext.getOpPackageName()); + return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts, + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" + @@ -1442,7 +1445,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().isVoiceMailNumber(accountHandle, number, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e); @@ -1464,7 +1467,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getVoiceMailNumber(accountHandle, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e); @@ -1485,7 +1488,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getLine1Number(accountHandle, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e); @@ -1506,7 +1509,8 @@ public class TelecomManager { public boolean isInCall() { try { if (isServiceConnected()) { - return getTelecomService().isInCall(mContext.getOpPackageName()); + return getTelecomService().isInCall(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling isInCall().", e); @@ -1531,7 +1535,8 @@ public class TelecomManager { public boolean isInManagedCall() { try { if (isServiceConnected()) { - return getTelecomService().isInManagedCall(mContext.getOpPackageName()); + return getTelecomService().isInManagedCall(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling isInManagedCall().", e); @@ -1711,7 +1716,8 @@ public class TelecomManager { public boolean isTtySupported() { try { if (isServiceConnected()) { - return getTelecomService().isTtySupported(mContext.getOpPackageName()); + return getTelecomService().isTtySupported(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get TTY supported state.", e); @@ -1735,7 +1741,8 @@ public class TelecomManager { public @TtyMode int getCurrentTtyMode() { try { if (isServiceConnected()) { - return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName()); + return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e); @@ -1925,7 +1932,8 @@ public class TelecomManager { ITelecomService service = getTelecomService(); if (service != null) { try { - service.showInCallScreen(showDialpad, mContext.getOpPackageName()); + service.showInCallScreen(showDialpad, mContext.getOpPackageName(), + mContext.getFeatureId()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); } @@ -1988,7 +1996,7 @@ public class TelecomManager { } try { service.placeCall(address, extras == null ? new Bundle() : extras, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#placeCall", e); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 204c37e9aa38..c54da6b4d527 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -35,12 +35,13 @@ interface ITelecomService { * * @param showDialpad if true, make the dialpad visible initially. */ - void showInCallScreen(boolean showDialpad, String callingPackage); + void showInCallScreen(boolean showDialpad, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount */ - PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage); + PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount @@ -56,12 +57,13 @@ interface ITelecomService { * @see TelecomServiceImpl#getCallCapablePhoneAccounts */ List<PhoneAccountHandle> getCallCapablePhoneAccounts( - boolean includeDisabledAccounts, String callingPackage); + boolean includeDisabledAccounts, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getSelfManagedPhoneAccounts */ - List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage); + List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage, + String callingFeatureId); /** * @see TelecomManager#getPhoneAccountsSupportingScheme @@ -123,17 +125,19 @@ interface ITelecomService { * @see TelecomServiceImpl#isVoiceMailNumber */ boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number, - String callingPackage); + String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getVoiceMailNumber */ - String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage); + String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getLine1Number */ - String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage); + String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getDefaultPhoneApp @@ -172,12 +176,12 @@ interface ITelecomService { /** * @see TelecomServiceImpl#isInCall */ - boolean isInCall(String callingPackage); + boolean isInCall(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#isInManagedCall */ - boolean isInManagedCall(String callingPackage); + boolean isInManagedCall(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#isRinging @@ -229,12 +233,12 @@ interface ITelecomService { /** * @see TelecomServiceImpl#isTtySupported */ - boolean isTtySupported(String callingPackage); + boolean isTtySupported(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getCurrentTtyMode */ - int getCurrentTtyMode(String callingPackage); + int getCurrentTtyMode(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#addNewIncomingCall @@ -249,7 +253,7 @@ interface ITelecomService { /** * @see TelecomServiceImpl#placeCall */ - void placeCall(in Uri handle, in Bundle extras, String callingPackage); + void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#enablePhoneAccount diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f39981fdf25d..aaafee29e24a 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index eb02ea6f5e40..97bcbc061f8a 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -18,14 +18,16 @@ package com.android.internal.telephony; import android.annotation.Nullable; import android.content.ContentResolver; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.RemoteException; +import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; @@ -76,7 +78,7 @@ public final class CarrierAppUtils { */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, - TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) { + TelephonyManager telephonyManager, int userId, Context context) { if (DEBUG) { Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); } @@ -85,6 +87,7 @@ public final class CarrierAppUtils { config.getDisabledUntilUsedPreinstalledCarrierApps(); ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); @@ -102,8 +105,8 @@ public final class CarrierAppUtils { * Manager can kill it, and this can lead to crashes as the app is in an unexpected state. */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, IPermissionManager permissionManager, - ContentResolver contentResolver, int userId) { + IPackageManager packageManager, IPermissionManager permissionManager, int userId, + Context context) { if (DEBUG) { Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); } @@ -114,15 +117,23 @@ public final class CarrierAppUtils { ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, null /* telephonyManager */, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); } + private static ContentResolver getContentResolverForUser(Context context, int userId) { + Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId), + 0); + return userContext.getContentResolver(); + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. */ + // Must be public b/c framework unit tests can't access package-private methods. @VisibleForTesting public static void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, @@ -142,9 +153,8 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed); List<String> enabledCarrierPackages = new ArrayList<>(); - - boolean hasRunOnce = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1; + boolean hasRunOnce = Settings.Secure.getInt(contentResolver, + Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1; try { for (ApplicationInfo ai : candidates) { @@ -259,8 +269,7 @@ public final class CarrierAppUtils { // Mark the execution so we do not disable apps again. if (!hasRunOnce) { - Settings.Secure.putIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId); + Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1); } if (!enabledCarrierPackages.isEmpty()) { diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index 22cbdaa0f133..a36ff9341275 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.text.TextUtils; import android.util.SparseIntArray; diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java index 2f3194214be6..45a563c09394 100644 --- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; import com.android.internal.telephony.HbpcdLookup.MccIdd; diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index b30258906368..afb9b6f52bdb 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -40,7 +40,7 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.Log; diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index 06c08f56aa1f..06c37288a1a6 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -24,7 +24,7 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.text.TextUtils; diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 80a55b2a1147..4109ca6bd7d0 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -28,7 +28,7 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; diff --git a/telephony/java/android/service/carrier/CarrierIdentifier.java b/telephony/java/android/service/carrier/CarrierIdentifier.java index af5bf7475f95..7957c258b54f 100644 --- a/telephony/java/android/service/carrier/CarrierIdentifier.java +++ b/telephony/java/android/service/carrier/CarrierIdentifier.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import com.android.internal.telephony.uicc.IccUtils; diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index 9753d8be4065..097041f672ac 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.content.Context; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 4d5713259a0f..6a622378dac7 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -3123,7 +3125,6 @@ public class CarrierConfigManager { * EAP-AKA: "0" * EAP-SIM: "1" * EAP-AKA_PRIME: "6" - * @hide */ public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; @@ -3398,12 +3399,17 @@ public class CarrierConfigManager { /** Prefix of all Ims.KEY_* constants. */ public static final String KEY_PREFIX = "ims."; - //TODO: Add configs related to IMS. + /** + * Delay in milliseconds to turn off wifi when IMS is registered over wifi. + */ + public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = + KEY_PREFIX + "wifi_off_deferring_time_int"; private Ims() {} private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); + defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_INT, 0); return defaults; } } diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java index 84be4e8b9ba4..719ba8d98773 100644 --- a/telephony/java/android/telephony/CbGeoUtils.java +++ b/telephony/java/android/telephony/CbGeoUtils.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.text.TextUtils; diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java index 534546921ebf..8e703fee3126 100644 --- a/telephony/java/android/telephony/CellIdentity.java +++ b/telephony/java/android/telephony/CellIdentity.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java index 2b1387c313ae..acb21f450243 100644 --- a/telephony/java/android/telephony/CellInfoCdma.java +++ b/telephony/java/android/telephony/CellInfoCdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java index 4f7c7a93d19b..79a9d44f36a2 100644 --- a/telephony/java/android/telephony/CellInfoGsm.java +++ b/telephony/java/android/telephony/CellInfoGsm.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java index 6d19261c8932..fed3ebf4af03 100644 --- a/telephony/java/android/telephony/CellInfoLte.java +++ b/telephony/java/android/telephony/CellInfoLte.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java index f1305f5ca768..58ff8c9558d9 100644 --- a/telephony/java/android/telephony/CellInfoTdscdma.java +++ b/telephony/java/android/telephony/CellInfoTdscdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java index ee5fec838d2d..33f6a555414c 100644 --- a/telephony/java/android/telephony/CellInfoWcdma.java +++ b/telephony/java/android/telephony/CellInfoWcdma.java @@ -18,7 +18,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.util.Objects; diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java index 11931996009c..2d0bd52f84ee 100644 --- a/telephony/java/android/telephony/CellLocation.java +++ b/telephony/java/android/telephony/CellLocation.java @@ -53,8 +53,7 @@ public abstract class CellLocation { /** * Create a new CellLocation from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method maybe used by external applications. * * @param bundle Bundle from intent notifier * @return newly created CellLocation diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index 199843905854..cab3b0cd3c47 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -20,7 +20,7 @@ import android.annotation.IntRange; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.util.Objects; diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index a9f3487a0880..28052aa93486 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index a6ba9c279ae3..2ef2a52977ff 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index d28d750c6011..4d67bcf536cf 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.IntRange; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java index f4a3dbb37988..3bd9d5810136 100644 --- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.annotation.NonNull; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index 34b13858f104..535e9520074d 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java index b10649c7208f..a6dedf761636 100644 --- a/telephony/java/android/telephony/NetworkScan.java +++ b/telephony/java/android/telephony/NetworkScan.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.os.RemoteException; diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index 8c5e10788b89..844289ce75d4 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java index 89b96654451e..214ab41ae4f2 100644 --- a/telephony/java/android/telephony/NetworkServiceCallback.java +++ b/telephony/java/android/telephony/NetworkServiceCallback.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 6e86a4211834..2f9e6ac0f9ff 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 66feb7bac25d..2c62d0667e19 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index 3350a3371504..1c58f8faf7cf 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -85,8 +87,7 @@ public class SignalStrength implements Parcelable { /** * Create a new SignalStrength from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method may be used by external applications. * * @param m Bundle from intent notifier * @return newly created SignalStrength diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index c0bc29d26c6c..c217b8b83c26 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 2c0a1c94dc8d..c24eeb74f6cd 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 321753bc1776..4510fede4e8e 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 607450b76917..843c0656efc3 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5868,7 +5868,10 @@ public class TelephonyManager { * @param AID Application id. See ETSI 102.221 and 101.220. * @param p2 P2 parameter (described in ISO 7816-4). * @return an IccOpenLogicalChannelResponse object. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) { return iccOpenLogicalChannel(getSubId(), AID, p2); } @@ -5899,7 +5902,10 @@ public class TelephonyManager { * @param p2 P2 parameter (described in ISO 7816-4). * @return an IccOpenLogicalChannelResponse object. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) { try { ITelephony telephony = getITelephony(); @@ -5927,7 +5933,10 @@ public class TelephonyManager { * iccOpenLogicalChannel. * @return true if the channel was closed successfully. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) { @@ -5954,7 +5963,10 @@ public class TelephonyManager { * @param channel is the channel id to be closed as returned by a successful * iccOpenLogicalChannel. * @return true if the channel was closed successfully. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public boolean iccCloseLogicalChannel(int channel) { return iccCloseLogicalChannel(getSubId(), channel); } @@ -5973,7 +5985,10 @@ public class TelephonyManager { * iccOpenLogicalChannel. * @return true if the channel was closed successfully. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public boolean iccCloseLogicalChannel(int subId, int channel) { try { ITelephony telephony = getITelephony(); @@ -6009,7 +6024,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at the end, or null if * there is an issue connecting to the Telephony service. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @Nullable @@ -6047,7 +6065,10 @@ public class TelephonyManager { * @param data Data to be sent with the APDU. * @return The APDU response from the ICC card with the status appended at * the end. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, int p2, int p3, String data) { return iccTransmitApduLogicalChannel(getSubId(), channel, cla, @@ -6076,7 +6097,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduLogicalChannel(int subId, int channel, int cla, int instruction, int p1, int p2, int p3, String data) { try { @@ -6112,7 +6136,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @NonNull @@ -6148,7 +6175,10 @@ public class TelephonyManager { * @param data Data to be sent with the APDU. * @return The APDU response from the ICC card with the status appended at * the end. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2, int p3, String data) { return iccTransmitApduBasicChannel(getSubId(), cla, @@ -6175,7 +6205,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduBasicChannel(int subId, int cla, int instruction, int p1, int p2, int p3, String data) { try { @@ -6203,7 +6236,10 @@ public class TelephonyManager { * @param p3 P3 value of the APDU command. * @param filePath * @return The APDU response. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3, String filePath) { return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath); @@ -6225,7 +6261,10 @@ public class TelephonyManager { * @param filePath * @return The APDU response. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2, int p3, String filePath) { try { @@ -6251,7 +6290,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card in hexadecimal format * with the last 4 bytes being the status word. If the command fails, * returns an empty string. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String sendEnvelopeWithStatus(String content) { return sendEnvelopeWithStatus(getSubId(), content); } @@ -6271,7 +6313,10 @@ public class TelephonyManager { * with the last 4 bytes being the status word. If the command fails, * returns an empty string. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String sendEnvelopeWithStatus(int subId, String content) { try { ITelephony telephony = getITelephony(); diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index 8e6c170f255b..a1d40e85fb10 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java index 93ccba1dd996..81a09c645070 100644 --- a/telephony/java/android/telephony/UiccAccessRule.java +++ b/telephony/java/android/telephony/UiccAccessRule.java @@ -15,6 +15,8 @@ */ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java index 414b9995fd58..d4a27d925208 100644 --- a/telephony/java/android/telephony/VoLteServiceState.java +++ b/telephony/java/android/telephony/VoLteServiceState.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Bundle; @@ -51,8 +53,7 @@ public final class VoLteServiceState implements Parcelable { /** * Create a new VoLteServiceState from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method is maybe used by external applications. * * @param m Bundle from intent notifier * @return newly created VoLteServiceState diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index dbfb6a2a0f2e..fab1bf2215af 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -28,7 +28,7 @@ import android.provider.Telephony; import android.provider.Telephony.Carriers; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.NetworkType; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 372bdf1c0f81..bff12b624ae8 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -31,7 +31,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.telephony.AccessNetworkConstants; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 11dc78a611ff..d33d3f9a5eee 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -22,7 +22,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.LinkProperties; import android.os.RemoteException; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.data.DataService.DataServiceProvider; import java.lang.annotation.Retention; diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index e793979a61c9..8220b16500de 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -28,7 +28,7 @@ import android.os.Message; import android.os.RemoteException; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.Annotation.ApnType; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index 16662652847d..cd3fc953f9d2 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -25,7 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java index 8d2049b97138..abfee61930ed 100644 --- a/telephony/java/android/telephony/ims/ImsConferenceState.java +++ b/telephony/java/android/telephony/ims/ImsConferenceState.java @@ -24,7 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telecom.Call; import android.telecom.Connection; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.Log; import java.util.HashMap; diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java index dcb9c9d5ec27..136a83e2eec9 100644 --- a/telephony/java/android/telephony/ims/ImsExternalCallState.java +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java @@ -24,7 +24,7 @@ import android.annotation.TestApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index 4f0f089dfa79..c96271432ea2 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -35,6 +35,8 @@ import android.telephony.ims.feature.RcsFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; +import com.android.internal.telephony.IIntegerConsumer; + import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -158,9 +160,20 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Register registration callback: IImsRcsController is null"); + throw new ImsException("Cannot find remote IMS service", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + c.setExecutor(executor); - throw new UnsupportedOperationException("registerImsRegistrationCallback is not" - + "supported."); + try { + imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder()); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } } /**{@inheritDoc}*/ @@ -171,8 +184,18 @@ public class ImsRcsManager implements RegistrationManager { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } - throw new UnsupportedOperationException("unregisterImsRegistrationCallback is not" - + "supported."); + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Unregister registration callback: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /**{@inheritDoc}*/ @@ -186,8 +209,23 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } - throw new UnsupportedOperationException("getRegistrationState is not" - + "supported."); + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Get registration state error: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> stateCallback.accept(result)); + } + }); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /**{@inheritDoc}*/ @@ -202,10 +240,25 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } - throw new UnsupportedOperationException("getRegistrationTransportType is not" - + "supported."); - } + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Get registration transport type error: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.getImsRcsRegistrationTransportType(mSubId, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> transportTypeCallback.accept(result)); + } + }); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } /** * Registers an {@link AvailabilityCallback} with the system, which will provide RCS diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java index 6b728599c7d3..2d2e63812fad 100644 --- a/telephony/java/android/telephony/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -22,7 +22,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl index e81bac0f6764..6f6aa44371fa 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl @@ -19,6 +19,9 @@ package android.telephony.ims.aidl; import android.net.Uri; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IRcsUceControllerCallback; +import android.telephony.ims.aidl.IImsRegistrationCallback; + +import com.android.internal.telephony.IIntegerConsumer; /** * Interface used to interact with the Telephony IMS. @@ -26,6 +29,13 @@ import android.telephony.ims.aidl.IRcsUceControllerCallback; * {@hide} */ interface IImsRcsController { + // IMS RCS registration commands + void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c); + void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c); + void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer); + void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer); + + // IMS RCS capability commands void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); boolean isCapable(int subId, int capability, int radioTech); diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index cfc803ca3639..9116a3bf3bde 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -19,7 +19,7 @@ package com.android.ims; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ProvisioningManager; import android.telephony.ims.aidl.IImsConfig; diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java index 1d6ec2d82eb7..8e86ff788a08 100644 --- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java +++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseIntArray; import com.android.internal.telephony.cdma.sms.UserData; diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java index f78c65ffb3aa..54c07cfd3428 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java +++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java @@ -123,32 +123,6 @@ public class TelephonyIntents { public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED; - /** - * Broadcast Action: The phone's signal strength has changed. The intent will have the - * following extra values:</p> - * <ul> - * <li><em>phoneName</em> - A string version of the phone name.</li> - * <li><em>asu</em> - A numeric value for the signal strength. - * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu). - * The following special values are defined: - * <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul> - * </li> - * </ul> - * - * <p class="note"> - * You can <em>not</em> receive this through components declared - * in manifests, only by exlicitly registering for it with - * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, - * android.content.IntentFilter) Context.registerReceiver()}. - * - * <p class="note"> - * Requires the READ_PHONE_STATE permission. - * - * <p class="note">This is a protected intent that can only be sent - * by the system. - */ - public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR"; - /** * Broadcast Action: The data connection state has changed for any one of the diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index e75c5933f1df..832502cae37d 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -20,7 +20,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.sysprop.TelephonyProperties; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.cdma.CdmaSmsCbProgramData; diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index b5af6467a0ad..cbf0f5c297e1 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -18,7 +18,7 @@ package com.android.internal.telephony.cdma.sms; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SmsCbCmasInfo; import android.telephony.cdma.CdmaSmsCbProgramData; import android.telephony.cdma.CdmaSmsCbProgramResults; diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 0681dc11066e..417aafd765ea 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -28,7 +28,7 @@ import static com.android.internal.telephony.SmsConstants.MessageClass; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.text.TextUtils; import com.android.internal.telephony.EncodeException; diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index eed9a86cf448..0dc740194034 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -21,7 +21,7 @@ import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; import android.graphics.Color; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.GsmAlphabet; diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 2bc129ae4840..091edd4dc0d9 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -19,15 +19,17 @@ android_test { static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"], test_suites: ["general-tests"], test_config: "RollbackTest.xml", + java_resources: [":com.android.apex.apkrollback.test_v2"], } java_test_host { name: "StagedRollbackTest", srcs: ["StagedRollbackTest/src/**/*.java"], libs: ["tradefed"], - static_libs: ["testng"], + static_libs: ["testng", "compatibility-tradefed"], test_suites: ["general-tests"], test_config: "StagedRollbackTest.xml", + data: [":com.android.apex.apkrollback.test_v1"], } java_test_host { @@ -37,3 +39,44 @@ java_test_host { test_suites: ["general-tests"], test_config: "MultiUserRollbackTest.xml", } + +genrule { + name: "com.android.apex.apkrollback.test.pem", + out: ["com.android.apex.apkrollback.test.pem"], + cmd: "openssl genrsa -out $(out) 4096", +} + +genrule { + name: "com.android.apex.apkrollback.test.pubkey", + srcs: [":com.android.apex.apkrollback.test.pem"], + out: ["com.android.apex.apkrollback.test.pubkey"], + tools: ["avbtool"], + cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)", +} + +apex_key { + name: "com.android.apex.apkrollback.test.key", + private_key: ":com.android.apex.apkrollback.test.pem", + public_key: ":com.android.apex.apkrollback.test.pubkey", + installable: false, +} + +apex { + name: "com.android.apex.apkrollback.test_v1", + manifest: "testdata/manifest_v1.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv1"], + installable: false, +} + +apex { + name: "com.android.apex.apkrollback.test_v2", + manifest: "testdata/manifest_v2.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv2"], + installable: false, +}
\ No newline at end of file diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 9e490f765eab..3877cc139a3e 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -435,11 +435,64 @@ public class StagedRollbackTest { // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks // so there is only one rollback to commit when testing native crashes. - RollbackManager rm = RollbackUtils.getRollbackManager(); + RollbackManager rm = RollbackUtils.getRollbackManager(); rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); } + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1", + APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); + private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", + APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); + private static final TestApp TEST_APP_A_V2_UNKNOWN = new TestApp("Av2Unknown", TestApp.A, 0, + /*isApex*/false, "TestAppAv2.apk"); + + @Test + public void testRollbackApexWithApk_Phase1() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + + int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() + .commit(); + InstallUtils.waitForSessionReady(sessionId); + } + + @Test + public void testRollbackApexWithApk_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.processUserData(TestApp.A); + + RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); + assertThat(available).isStaged(); + assertThat(available).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + + RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); + RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); + assertThat(committed).isNotNull(); + assertThat(committed).isStaged(); + assertThat(committed).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2); + assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1); + + // Note: The app is not rolled back until after the rollback is staged + // and the device has been rebooted. + InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + } + + @Test + public void testRollbackApexWithApk_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + } + private static void runShellCommand(String cmd) { ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() .executeShellCommand(cmd); diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 91577c202df9..6daa6bc723c4 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -19,6 +19,7 @@ package com.android.tests.rollback.host; import static org.junit.Assert.assertTrue; import static org.testng.Assert.assertThrows; +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -27,6 +28,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.util.concurrent.TimeUnit; /** @@ -48,14 +50,32 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { phase)); } + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + @Before public void setUp() throws Exception { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + getDevice().executeShellCommand( + "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex " + + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); getDevice().reboot(); } @After public void tearDown() throws Exception { runPhase("testCleanUp"); + + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + getDevice().executeShellCommand( + "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex " + + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); + getDevice().reboot(); } /** @@ -184,6 +204,28 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testRollbackDataPolicy_Phase3"); } + /** + * Tests that userdata of apk-in-apex is restored when apex is rolled back. + */ + @Test + public void testRollbackApexWithApk() throws Exception { + getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); + final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; + final File apex = buildHelper.getTestFile(fileName); + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase1"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase2"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase3"); + } + private void crashProcess(String processName, int numberOfCrashes) throws Exception { String pid = ""; String lastPid = "invalid"; diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml new file mode 100644 index 000000000000..f21ec899eb69 --- /dev/null +++ b/tests/RollbackTest/testdata/AndroidManifest.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.apex.apkrollback.test"> + <!-- APEX does not have classes.dex --> + <application android:hasCode="false" /> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/> +</manifest> + diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json new file mode 100644 index 000000000000..1762fc6764cf --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v1.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 1 +} diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json new file mode 100644 index 000000000000..c5127b9c3023 --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v2.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 2 +} diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp new file mode 100644 index 000000000000..8a13dbc52c66 --- /dev/null +++ b/tests/TaskOrganizerTest/Android.bp @@ -0,0 +1,22 @@ +// +// Copyright (C) 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. +// + +android_test { + name: "TaskOrganizerTest", + srcs: ["**/*.java"], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml new file mode 100644 index 000000000000..0cb6c10a7ff5 --- /dev/null +++ b/tests/TaskOrganizerTest/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.taskembed"> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <application> + <service android:name=".TaskOrganizerPipTest" + android:exported="true"> + </service> + </application> +</manifest> diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java new file mode 100644 index 000000000000..6ffa19d4ec98 --- /dev/null +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 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.test.taskembed; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.Service; +import android.app.WindowConfiguration; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.ITaskOrganizer; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +public class TaskOrganizerPipTest extends Service { + static final int PIP_WIDTH = 640; + static final int PIP_HEIGHT = 360; + + class PipOrgView extends SurfaceView implements SurfaceHolder.Callback { + PipOrgView(Context c) { + super(c); + getHolder().addCallback(this); + setZOrderOnTop(true); + } + @Override + public void surfaceCreated(SurfaceHolder holder) { + try { + ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer, + WindowConfiguration.WINDOWING_MODE_PINNED); + } catch (Exception e) { + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + + void reparentTask(IWindowContainer wc) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + SurfaceControl leash = null; + try { + leash = wc.getLeash(); + } catch (Exception e) { + // System server died.. oh well + } + t.reparent(leash, getSurfaceControl()) + .setPosition(leash, 0, 0) + .apply(); + } + } + + PipOrgView mPipView; + + class Organizer extends ITaskOrganizer.Stub { + public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) { + mPipView.reparentTask(wc); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); + try { + ActivityTaskManager.getService().applyContainerTransaction(wct); + } catch (Exception e) { + } + } + public void taskVanished(IWindowContainer wc) { + } + public void transactionReady(int id, SurfaceControl.Transaction t) { + } + } + + Organizer mOrganizer = new Organizer(); + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); + wlp.setTitle("TaskOrganizerPipTest"); + wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + + FrameLayout layout = new FrameLayout(this); + ViewGroup.LayoutParams lp = + new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT); + mPipView = new PipOrgView(this); + layout.addView(mPipView, lp); + + WindowManager wm = getSystemService(WindowManager.class); + wm.addView(layout, wlp); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } +} diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py index 46105f4d66b0..0b2077d9bba0 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ b/tools/hiddenapi/generate_hiddenapi_lists.py @@ -149,7 +149,12 @@ def extract_package(signature): The package name of the class containing the field/method. """ full_class_name = signature.split(";->")[0] - package_name = full_class_name[1:full_class_name.rindex("/")] + # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy + if (full_class_name[0] != "L"): + raise ValueError("Expected to start with 'L': %s" % full_class_name) + full_class_name = full_class_name[1:] + # If full_class_name doesn't contain '/', then package_name will be ''. + package_name = full_class_name.rpartition("/")[0] return package_name.replace('/', '.') class FlagsDict: diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp index 79dce4a8ce09..7b2ca9a65242 100644 --- a/tools/lock_agent/Android.bp +++ b/tools/lock_agent/Android.bp @@ -25,6 +25,7 @@ cc_binary_host { srcs: ["agent.cpp"], static_libs: [ "libbase", + "liblog", "libz", "slicer", ], diff --git a/wifi/Android.bp b/wifi/Android.bp index 09d5386dcfc5..180368cbd9f7 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -67,10 +67,15 @@ java_library { optimize: { enabled: false }, + hostdex: true, // for hiddenapi check visibility: [ "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed "//frameworks/opt/net/wifi/service:__subpackages__", ] + test_access_hidden_api_whitelist, + apex_available: [ + "com.android.wifi", + "test_com.android.wifi", + ], plugins: ["java_api_finder"], } diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 71942f0f80f4..f490766559de 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -250,4 +250,6 @@ interface IWifiManager void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName); int calculateSignalLevel(int rssi); + + List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults); } diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 65e9b7910e5e..2b7f8af728d7 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -182,6 +182,12 @@ public final class SoftApConfiguration implements Parcelable { private final @SecurityType int mSecurityType; /** + * Delay in milliseconds before shutting down soft AP when + * there are no connected devices. + */ + private final int mShutdownTimeoutMillis; + + /** * Security types we support. */ /** @hide */ @@ -213,7 +219,7 @@ public final class SoftApConfiguration implements Parcelable { /** Private constructor for Builder and Parcelable implementation. */ private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid, @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel, - @SecurityType int securityType, int maxNumberOfClients) { + @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) { mSsid = ssid; mBssid = bssid; mPassphrase = passphrase; @@ -222,6 +228,7 @@ public final class SoftApConfiguration implements Parcelable { mChannel = channel; mSecurityType = securityType; mMaxNumberOfClients = maxNumberOfClients; + mShutdownTimeoutMillis = shutdownTimeoutMillis; } @Override @@ -240,13 +247,14 @@ public final class SoftApConfiguration implements Parcelable { && mBand == other.mBand && mChannel == other.mChannel && mSecurityType == other.mSecurityType - && mMaxNumberOfClients == other.mMaxNumberOfClients; + && mMaxNumberOfClients == other.mMaxNumberOfClients + && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis; } @Override public int hashCode() { return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid, - mBand, mChannel, mSecurityType, mMaxNumberOfClients); + mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis); } @Override @@ -261,6 +269,7 @@ public final class SoftApConfiguration implements Parcelable { sbuf.append(" \n Channel =").append(mChannel); sbuf.append(" \n SecurityType=").append(getSecurityType()); sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients); + sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis); return sbuf.toString(); } @@ -274,6 +283,7 @@ public final class SoftApConfiguration implements Parcelable { dest.writeInt(mChannel); dest.writeInt(mSecurityType); dest.writeInt(mMaxNumberOfClients); + dest.writeInt(mShutdownTimeoutMillis); } @Override @@ -289,7 +299,7 @@ public final class SoftApConfiguration implements Parcelable { in.readString(), in.readParcelable(MacAddress.class.getClassLoader()), in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(), - in.readInt()); + in.readInt(), in.readInt()); } @Override @@ -316,19 +326,6 @@ public final class SoftApConfiguration implements Parcelable { return mBssid; } - // TODO: Remove it after update the caller - /** - * Returns String set to be passphrase for the WPA2-PSK AP. - * {@link #setWpa2Passphrase(String)}. - */ - @Nullable - public String getWpa2Passphrase() { - if (mSecurityType == SECURITY_TYPE_WPA2_PSK) { - return mPassphrase; - } - return null; - } - /** * Returns String set to be passphrase for current AP. * {@link #setPassphrase(String, @SecurityType int)}. @@ -381,6 +378,15 @@ public final class SoftApConfiguration implements Parcelable { } /** + * Returns the shutdown timeout in milliseconds. + * The Soft AP will shutdown when there are no devices associated to it for + * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}. + */ + public int getShutdownTimeoutMillis() { + return mShutdownTimeoutMillis; + } + + /** * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a * Soft AP. * @@ -396,6 +402,7 @@ public final class SoftApConfiguration implements Parcelable { private int mChannel; private int mMaxNumberOfClients; private int mSecurityType; + private int mShutdownTimeoutMillis; /** * Constructs a Builder with default values (see {@link Builder}). @@ -409,6 +416,7 @@ public final class SoftApConfiguration implements Parcelable { mChannel = 0; mMaxNumberOfClients = 0; mSecurityType = SECURITY_TYPE_OPEN; + mShutdownTimeoutMillis = 0; } /** @@ -425,6 +433,7 @@ public final class SoftApConfiguration implements Parcelable { mChannel = other.mChannel; mMaxNumberOfClients = other.mMaxNumberOfClients; mSecurityType = other.mSecurityType; + mShutdownTimeoutMillis = other.mShutdownTimeoutMillis; } /** @@ -435,7 +444,8 @@ public final class SoftApConfiguration implements Parcelable { @NonNull public SoftApConfiguration build() { return new SoftApConfiguration(mSsid, mBssid, mPassphrase, - mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients); + mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients, + mShutdownTimeoutMillis); } /** @@ -482,22 +492,6 @@ public final class SoftApConfiguration implements Parcelable { return this; } - // TODO: Remove it after update the caller - /** - * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase. - * When set to null, an open network is created. - * <p> - * - * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK - * configuration. - * @return Builder for chaining. - * @throws IllegalArgumentException when the passphrase is the empty string - */ - @NonNull - public Builder setWpa2Passphrase(@Nullable String passphrase) { - return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK); - } - /** * Specifies that this AP should use specific security type with the given ASCII passphrase. * @@ -643,5 +637,30 @@ public final class SoftApConfiguration implements Parcelable { mMaxNumberOfClients = maxNumberOfClients; return this; } + + /** + * Specifies the shutdown timeout in milliseconds. + * The Soft AP will shut down when there are no devices connected to it for + * the timeout duration. + * + * Specify a value of 0 to have the framework automatically use default timeout + * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay} + * + * <p> + * <li>If not set, defaults to 0</li> + * <li>The shut down timout will apply when + * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li> + * + * @param timeoutMillis milliseconds of the timeout delay. + * @return Builder for chaining. + */ + @NonNull + public Builder setShutdownTimeoutMillis(int timeoutMillis) { + if (timeoutMillis < 0) { + throw new IllegalArgumentException("Invalid timeout value"); + } + mShutdownTimeoutMillis = timeoutMillis; + return this; + } } } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index e870481ccf26..1baab12b8ab5 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1366,6 +1366,36 @@ public class WifiManager { } /** + * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion} + * matching the given list of {@link ScanResult}. + * + * An available {@link WifiNetworkSuggestion} must satisfy: + * <ul> + * <li> Matching one of the {@link ScanResult} from the given list. + * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set + * to true. + * </ul> + * + * @param scanResults a list of scanResult. + * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion} + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD + }) + @NonNull + public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( + @NonNull List<ScanResult> scanResults) { + try { + return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given * list of ScanResult. * diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index d91efbccc2de..3c13562d6952 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -589,4 +589,10 @@ public class BaseWifiService extends IWifiManager.Stub { public int calculateSignalLevel(int rssi) { throw new UnsupportedOperationException(); } + + @Override + public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( + List<ScanResult> scanResults) { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index acd334355806..eeea7e2a6cd8 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -117,6 +117,7 @@ public class SoftApConfigurationTest { .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .setMaxNumberOfClients(10) + .setShutdownTimeoutMillis(500000) .build(); assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( @@ -125,6 +126,7 @@ public class SoftApConfigurationTest { assertThat(original.getChannel()).isEqualTo(149); assertThat(original.isHiddenSsid()).isEqualTo(true); assertThat(original.getMaxNumberOfClients()).isEqualTo(10); + assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000); SoftApConfiguration unparceled = parcelUnparcel(original); assertThat(unparceled).isNotSameAs(original); @@ -230,4 +232,10 @@ public class SoftApConfigurationTest { .build(); } + @Test(expected = IllegalArgumentException.class) + public void testInvalieShutdownTimeoutMillis() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setShutdownTimeoutMillis(-1) + .build(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 5ac50a0d1c97..4b837184dc9a 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -2185,4 +2185,18 @@ public class WifiManagerTest { result = WifiManager.parseDppChannelList(channelList); assertEquals(result.size(), 0); } + + /** + * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults. + */ + @Test + public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception { + List<WifiConfiguration> testResults = new ArrayList<>(); + testResults.add(new WifiConfiguration()); + + when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class))) + .thenReturn(testResults); + assertEquals(testResults, mWifiManager + .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>())); + } } |